Month of Testing: Advanced Topics in Salesforce Unit Testing (Part 3 of 3)

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:

Name,Website,Phone,BillingStreet,BillingCity,BillingState,BillingPostalCode,BillingCountry
sForceTest1,http://www.sforcetest1.com,(415) 901-7000,The Landmark @ One Market,San Francisco,CA,94105,US
sForceTest2,http://www.sforcetest2.com,(415) 901-7000,The Landmark @ One Market Suite 300,San Francisco,CA,94105,US
sForceTest3,http://www.sforcetest3.com,(415) 901-7000,1 Market St,San Francisco,CA,94105,US

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.

@isTest
private class MyClass_Tests {

@testSetup
static void loadTestDataFromStaticResource(){
//Lets load the account csv!
List<sObject> accts = Test.loadData(Account.sObjectType, 'MyClass_tests.accounts.csv');
System.assertEquals(accts.size(), 3, 'Unable to create some accounts from CSV file');
insert accts;
}
}

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:

Name,Website,Phone
Puppers,http://www.pupsRock.com,(919) 555-5555
Kittehs,http://www.kittehsRock.com,(317) 555-5555
FirstName,LastName,Email
Pupper,Lover,pupperLover@pupsRock.com
Pupper,Mom,PupperMom@pupsRock.com
Kitteh,Lover,kittehLover@kittehsRock.com

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:

Id,Name,Website,Phone
1,Puppers,http://www.pupsRock.com,(919) 555-5555
2,Kittehs,http://www.kittehsRock.com,(317) 555-5555
Id,AccountId,FirstName,LastName,Email
100,1,Pupper,Lover,pupperLover@pupsRock.com
101,1,Pupper,Mom,PupperMom@pupsRock.com
102,2,Kitteh,Lover,kittehLover@kittehsRock.com

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:

  1. 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:
    1. Use Test.isRunningTest() in your code to return some information without making a callout
    2. Mock the callout itself
  2. 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.

Mocking

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:

public class MostBasicMock implements HttpCalloutMock {
    public HTTPResponse respond(HttpRequest req) {
        //construct and return a HttpResponse Object here.
        return false;
    }
}

Once you’ve written your implementation class, you can use this mock in your tests like this:

@isTest
private class AwesomeSauce_Tests(){
    @isTest static void AwesomeSauce_callout_test(){
        //test data setup and sanity checks
        Test.setMock(HttpCalloutMock.class, new MostBasicMock());
        Test.startTest();
        AwesomeSauce.getExternalData();
        Test.stopTest();
        //an Assertion a day keeps the doctor away
    }
}

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:

@isTest
public class AwesomeMockFactory implements HttpCalloutMock{
    Protected Integer responseCode;
    Protected String status;
    Protected String bodyAsString;
    Protected Map<String,String> responseHeaders;
    
    Public AwesomeMockFactory(Integer code, String status, String bodyAsString, Map<String,String> headers){
        this.responseCode = code;
        this.status = status;
        this.bodyAsString = bodyAsString;
        this.responseHeaders = headers;
    }
    
    public HttpResponse respond(HttpRequest req) {
        HttpResponse res = new HttpResponse();
        res.setStatusCode(this.code);
        res.setStatus(this.status);
        res.body = this.bodyAsString;
        return res;
    }
}

With this in place, we’re free to construct the response we need inside the test.

@isTest
private class AwesomeSauce_Tests{
    @isTest static void awesomeSauce_callout_tests(){
        //test data setup and sanity checking
        AwesomeMockFactory theMock = new AwesomeMockFactory(200, 'OK', 'yay!', new Map<String,String>());
        Test.setMock(HttpCalloutMock.class, theMock);
        Test.startTest();
        AwesomeSauce.getExternalData();
        Test.stopTest();
        //seriously, write some assertions.
    }
}

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:

@isTest
public class MostExcellentMock implements HttpCalloutMock{
    Protected List<HttpResponse> orderedResponses;
    
    public MostExcellentMock(List<HttpResponse> orderedResponses){
        this.orderedResponses = orderedResponses;
    }
    
    public HttpResponse respond(HttpRequest req){
        return this.orderedResponses.remove(0);
    }
}

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:

  1. Implement the StubProvider interface.
  2. Instantiate the mock object
  3. Start the test, passing in the stub’d object / method as a dependency.

Implementing the StubProvider interface is straightforward. There is only a single method, handleMethodCall() that you need to define.

@isTest
public class MockityMock implements System.StubProvider {
    public Object handleMethodCall(Object stubbedObject, String stubbedMethodName,
                                    Type returnType, List<Type> listOfParamTypes, 
                                    List<String> listOfParamNames,
                                    List<Object> listOfArgs){
        switch on stubbedMethodName
            when 'awesomeMethod1' {
                return 'yay, called awesomeMethod1';
            }
            when 'addTwoNumbers' {
                return 4;
            }
            //ye 'old other options
         }
     }
}

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:

AwesomelyMockable am = Test.createStub(AwesomelyMockable.class, new MockMock());

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.

public class IEQOAccount {
    //our example account wrapper
    public Boolean isNonProfit(){
        //do something to determine if this account is a non-profit
        return false;
    }
}
public class TaxUtil {
    //A method we can inject our Account wrapper into
    public static double getTaxRate(IEQOAccount acct){
        if(acct.isNonProfit()){
            return 0.00;
        else {
            return 300.00; //yeah, taxes stink.
        }
    }
}
public class Invoice {
    //the class we're interested in testing
    IEQOAccount acct;
    Opportunity2__c oppty;
    Double totalTax = 0.00;
    
    public Invoice (IEQOAccount acct){
        this.acct = acct;
        this.oppty = new Opportunity(Amount= 100.00);
    }
    
    public addTaxToInvoice(){
        totalTax = oppty.ammount * TaxUtil.getTaxRate(this.acct);
    }
}

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:

@isTest
private class Invoice_tests{
    @isTest private static addTaxToInvoice_PositiveNonProfit{
        // setup and sanity check test data.
        IEQOAccount mockAcct = Test.createStub(IEQOAccount.class, new MockityMock());
        Invoice i = new Invoice(mockAcct);
        Test.startTest();
        i.addTaxToInvoice();
        Test.stopTest();
        System.assertEquals(0.00, i.totalTax, 'expected the tax to be 0, because this is a nonprofit account');
    }
}

When 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:

  1. System types
  2. Classes with only private constructors
  3. Classes that implement the Batchable interface.

Now what?

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 HttpCalloutMock generator?

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?

I look forward to hearing what you have to say about these questions and these ideas in practice. You can reach back out to us at @SalesforceDevs or myself at @codefriar

Keep the conversation going on Twitter with #MonthOfTesting.

Trailhead module

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.

Leave your comments...

Month of Testing: Advanced Topics in Salesforce Unit Testing (Part 3 of 3)