This is the third part of a three-part blog series about testing. We will be talking about:
These advanced patterns and ideas help increase your test’s effectiveness. They focus on improving data generation, speeding up test execution and improving isolation. They’re definitely not required for effective tests, but they do help.
Advanced data generation
In our last post, we created test data in a
@testSetup annotated method. This works fine, but can make test maintenance tedious. Consider the work required to add a validation rule, or require a field on the account object. A test factory can generate the account object, but we’ll likely have to edit code and redeploy it. To make things easier, we can create data and store it in a static resource. Then during our test’s
@testSetup method, we load that data from the static resource. This technique enhances the definitiveness of our tests. It helps by isolating data issues from code issues. Let’s take a look at how this works in three easy steps. Most importantly, it makes maintaining your tests easier. How? By using static resources, any admin you work with can add new data at any time. Add a new permission set? Just add the appropriate data in the CSV and your tests will test the new permission set when run.
Let’s say you need to load account data for your tests. Rather than creating known edge-case data to accompany generic test data from your TestFactory, create a CSV file like this:
Note: Your CSV file must have field names as the first row! Once you’ve created this CSV file, you can save it in a static resource.
I tend to name my static resources after the test class that uses it, with the name of the object appended. i.e.: MyClass_tests.Account.csv
Once you’ve got a static resource with your CSV data, it’s simple to load that data in your test class.
This style of creating test data has some unique advantages. For instance, your product management and QA teams can now contribute test data. If a bug arises, your QA team can add a row to the CSV, upload it and your tests will run with that data! You’ll still have to deploy the new static resource version. Static resources, however, are easier and faster to deploy.
Of course, this may seem a bit limited, since tests most often need many objects, all related to one another. Thankfully, there’s a simple trick to solve this issue as well. Let’s say we need to insert accounts and contacts related to those accounts. Your CSV’s might look something like this:
If we add some extra fields, we can associate the contacts with the appropriate accounts. We need to add ID fields to both CSV’s and an AccountId field to the contacts CSV, like this:
As long as we load accounts before contacts, the contacts will map to the account specified. The contacts’ AccountId determines which account its related to. Nifty trick eh? To save headaches in the future, it can be helpful to document the proper load order in your code’s comments.
Mocking: it’s not just for comedians
One of the more interesting and useful concepts in unit testing is mocking. Mocking helps you test code with external dependencies. A mock object stands in-place of a real object instance. If your code calls many methods from another class, you might mock the instance of the external class. The stub is similar to the idea of a mock. While a mock object generally refers to or fakes the entire object, a stub fakes a specific method. These can be confusing, so let’s discuss a couple of concrete use cases:
- You’re testing a unit of code that makes an HTTP callout. Since you’re not allowed to make callouts in a test context, you’re forced into one of two paths:
- Use Test.isRunningTest() in your code to return some information without making a callout
- Mock the callout itself
- You’re testing a unit of code that makes use of other units of code. These external dependencies aren’t what the code you’re testing cares about. For example, let’s say you’re testing some code related to calculating taxes. As part of that calculation you need to get a user’s tax rate. You’re focused on testing the tax calculation. You’re not focused on the process of getting a users’ tax rate. In a situation like this, it makes sense to mock the user’s tax rate lookup.
Our first example may not occur all that often, but depending on your org, the second scenario can be common. Learning to use mocks and stubs helps ensure that you’re testing the right code. Furthermore it isolates your code from failures in other parts of the codebases. Mocked objects and stubbed methods always return the value you specify. Because of this, tests using them won’t falsely break when, say, your tax lookup logic changes. Mocks and stubs can also help the execution speed of your tests. This arises from the system forgoing instantiating full objects. It also doesn’t have to load data or call execute any of the methods that you’ve stubbed. For this reason, it’s useful to stub “expensive” or time-consuming methods.
So how do we mock and stub on the Lightning Platform? There are two key interfaces for mocking and stubbing. First is the
HttpObjectMock . This is, as you might imagine, is for mocking HTTP objects. This allows you to write operational code without littering it with
if(Test.isRunningTest()) calls. To mock your callouts is a two-step process. First, you’ll need to create a class that implements the
HTTPCalloutMock interface. Secondly, you’ll need to use the
Test.setMock() method in your test code. Lets look at an example callout mock implementation:
Once you’ve written your implementation class, you can use this mock in your tests like this:
This works great for the occasional callout. However, it can be tedious and boiler-platey if you have lots of callouts. Thankfully, there’s an feature that makes it this much easier to use. Note in the unit test above, we have to construct our
mockClass when we call
setMock(). This means we can also pass data into our constructor and in doing so, change its behavior. Refactoring our implementing class to use a constructor creates a mock object factory:
With this in place, we’re free to construct the response we need inside the test.
This helps us avoid writing untold numbers of single-use classes for testing. But what do we do if we have a series of callouts in the code we’re testing? We can only call
setMock() once per test. The simplest solution is to provide an alternative constructor that accepts a list of responses. Then in our respond method, pull the first response off the list and return it. It would look something like this:
As our code makes callouts, the respond method returns successive mocked responses. You can also write your response method to respond based on request object. This allows you to return different data based on query parameters or HTTP method. That, as they say, is an exercise for you dear reader.
Stubbing: with great flexibility comes great architecture
While mocking entire
HttpResponse objects is straightforward, it’s limited to HTTP objects. Stubs are more complex, but more flexible. To use stubs, you have to be able to not only create stubs, but inject them into your code. This enforces a certain architecture to your code. You’ll have to code in a way that allows you to into the code you’re testing. Stubs have three steps to their use:
- Implement the
- Instantiate the mock object
- Start the test, passing in the stub’d object / method as a dependency.
StubProvider interface is straightforward. There is only a single method,
handleMethodCall() that you need to define.
Note: You can construct this method to respond based on any of the input parameters. In other words, you can use the stubbed method name to decide what information to return. Likewise, you can use the list of parameter types, or the passed parameters themselves.
Once you’ve implemented the
StubProvider interface, you can create an instance using:
This will give you a mock object, which you can now dependency inject into any code that requires it. Sounds simple enough, right? Let’s look at a concrete example. Here we’ll mock an account wrapper in a test that utilizes a wrapper method.
In this example, we have a method on the
IEQQAccount class that returns true if the account is a non-profit. Simple enough. While we should definitely unit test that method, it is superfluous to the Invoice class. This is a situation where we can mock the
IEQQAccount object. In particular, we can stub the
isNonProfit() method. To build our mock, lets change the
MockityMock code from above:
Creating the stub and injecting it into our tested code works like this:
getTaxRate() calls the
isNonProfit() method the response comes from the
mockAcct object. Why? Because we’ve injected the
mockAcct object and we’ve coded the stub to always return true when calling the
isNonProfit() method. This means we don’t need to create an account object and insert it to test! It also means we’re not querying for the account anywhere in the test’s execution. However, it does mean we need to architect our code to accept dependency injected objects. Designing your code with this in mind becomes crucial. Instead of creating the class first, imagine creating its MockGenerator first. Define method signatures in a MockGenerator for your class, then code the implementation. This ensures you’ll be able to stub methods from your class in other class’ unit tests.
Some limitations apply
Despite the flexibility of the Stub API, there are some limitations. Notice in our example I made
isNonProfit is a method, instead of a property. That’s because you cannot stub a property. Likewise, you cannot stub static or private methods. If you consider the dependency injection basis of the API, this makes sense. It provides you the ability to inject a mock instance of an object with stub’d methods. There’s nothing to inject if it’s a property, or a static method. Additionally, you can’t stub:
- System types
- Classes with only private constructors
- Classes that implement the Batchable interface.
When we last left our testing discussion, I challenged you to go look through your codebase. To identify some code that was under tested or ineffectively tested. To whiteboard what a positive, negative and permissions-based test suite would look like. Find any bugs? Er, unexpected consequences of different permissions? Did you find negative tests?
In this post we’ve talked about some advanced tools for writing more effective tests — specifically, better test-data generation and mocking. This time, I’d like to challenge you to pick a class and build a mock generator for it. Refactor as necessary. Look at what kinds of architecture changes you’d need to enable dependency injection? How do you feel about those changes? Do you see benefit in them? Do you have callout code you can test with an
Additionally, I’d like to challenge you to start focusing your tests around CSV data. Get your QA team to join in the creation of the test artifacts. Does this change your team’s understanding of where engineering and QA come together? Does it affect your definition of done?
Keep the conversation going on Twitter with #MonthOfTesting.
About the author
Kevin Poorman works as a Senior Developer Evangelist at Salesforce. He focuses on testing, IOT, mobile, and integrations on the Lightning Platform. You can pester him on Twitter @codefriar.