It’s finally generally available with the Spring ’17 release; the Apex stub API! This is really going to make your unit tests easier to write and enable you to write more meaningful tests.

In this post I’ll cover why this is such a big deal and how you can incorporate it into your unit tests going forward.

So what is stubbing?

Stubbing is the process of telling Salesforce instead of executing a piece of business logic when unit tests are ran, run another piece of logic instead. What is key is that your Apex code isn’t aware that parts have been switched out and there is no conditional logic embedded in the production code which states if unit tests are running do something else instead.

The benefits for you

Let’s just highlight a few scenarios where this will really help you when writing unit tests:

  • Complex classes which call other layers in the application requires lots of test data setup. You end up having to test other areas of the application as part of your tests, so they aren’t really unit tests.
  • Different scenarios to be tested are difficult to recreate either due to complex test data setup or very difficult to produce.
  • Speed. The stubbing API allows you to avoid creating test data in the database, which ultimately is way faster.
  • Avoid having to use ugly Test.isRunningTest() statements within your production code.

How does it work?

First of all if you want to take advantage of the stubbing API then you need to familiarize yourself with dependency injection.

Dependency injection is the process of injecting dependencies into your classes or methods instead of calling the concrete classes directly from within the classes or methods. It’s a technique which enables you have decoupled code and creates more opportunities for better unit testing.

Small example

Let’s cover a small example where we have a class called OpprtunitiesService. This class is responsible for creating new opportunities based upon an accounts purchase history. If the customer has purchased a lot in the past (i.e. the account has a lot of closed won opportunities), then we set up the new opportunity with a better discount than if the customer hasn’t purchased much in the past. This discount information is provided by another class called DiscountsService.

public with sharing class OpportunitiesService {

   public Opportunity createOpportunity(Id accountId){
      Opportunity o = new Opportunity(
         Name = 'New Opportunity',
         StageName = 'New',
         Discount__c = new DiscountsService().getDiscount(accountId)
      );
      insert o;
      return o;
   }

}

Now let’s have a look at how might our DiscountsService may look like:

public with sharing class DiscountsService {

   public Decimal getDiscount(Id accountId){
      Integer numClosedWon = [SELECT
                                   COUNT()
                              FROM
                                   Opportunity
                              WHERE
                                   AccountId = :accountId
                              AND
                                   IsClosed = true
                              AND
                                   IsWon = true];

      Decimal discount = 0;

      if(numClosedWon >= 10 && numClosedWon < 20) discount = 10; 
      if(numClosedWon >= 20 && numClosedWon < 50) discount = 20; 
      if(numClosedWon >= 50) discount = 30;

      return discount;
   }

} 

Now if we really want to test our OpportunitiesService class we also want to test creating opportunities with different discount amounts. Perhaps the business added validation rules which take into account the discount amount?

So if we were to set out testing creating new opportunities with various different discount amounts then we would have to create historical opportunities which are closed won. This is a slow and laborious process of determining what data is required to be created, which fields need to be populated and writing the unit test code to set this data up. Not fun!

Enter the stub API

If you’re not used to stubbing then the stubbing API at first may seem confusing, but I’ll try my best to explain the different aspects of how all of the pieces fit together.

The main concept of the stubbing API is a stub provider. This is a class which you will create and it will only have one method inside it that you have to define. When we set up our unit tests we will create a mock of the DiscountsService and inject it into the OpportunitiesService.

But first, let’s create the bare skeleton class for our stub provider.

@isTest
global class MockDiscountsService implements StubProvider {

   global Object handleMethodCall(Object stubbedObject,
               String stubbedMethodName,
                     System.Type returnType,
                           List<System.Type> listOfParamTypes,
                                 List<String> listOfParamNames,
                                       List<Object> listOfArgs){
      return null;
   }

}

What is going on in the above code?

The method which we have to define in our custom stub provider will be called whenever there will be a call to the DiscountsService. The method is responsible for determining which method was called, with which arguments and the supplied values and then deciding how to return a value, if necessary.

Let’s me explain what each method argument means and how you’d use them:

  • Object stubbedObject – this is the stubbed object instance.
  • String stubbedMethodName – this is the name of the method which was called.
  • System.Type returnType – this is the return type of the method which was called.
  • List listOfParamTypes – this is the list of parameter types which the method which was called has.
  • List listOfParamNames – this is the list of parameter names which the method which was called has.
  • List listOfArgs – this is the list of arguments which were supplied to the method which was called.

Now we have the basic structure in place we are going to set up our stub provider to return different discount amounts to allow different scenarios to be tested in our OpportunitiesService class.

@isTest
global class MockDiscountsService implements StubProvider {
   
   Integer amount;

   public MockDiscountsService(Integer amount){
      this.amount = amount;
   }

   global Object handleMethodCall(Object stubbedObject,
               String stubbedMethodName,
                     System.Type returnType,
                           List<System.Type> listOfParamTypes,
                                 List<String> listOfParamNames,
                                       List<Object> listOfArgs){
      return null;
   }

}

Above you can see we’ve added a constructor to the stub provider which accepts an integer which will influence how many closed won opportunities should be set to influence the discount which should be returned.

We can now add the logic into the stub provider so that when the getDiscount method is called we can return a suitable discount amount.

@isTest
global class MockDiscountsService implements StubProvider {
   
   Integer amount;

   public MockDiscountsService(Integer amount){
      this.amount = amount;
   }

   global Object handleMethodCall(Object stubbedObject,
               String stubbedMethodName,
                     System.Type returnType,
                           List<System.Type> listOfParamTypes,
                                 List<String> listOfParamNames,
                                       List<Object> listOfArgs){

      if(stubbedMethodName == 'getDiscount'){
         Decimal discount = 0;
      
         if(amount >= 10 && amount < 20) discount = 10;
         if(amount >= 20 && amount < 50) discount = 20;
         if(amount >= 50) discount = 30;

         return discount;
      }
      
      return null;
   }

}

At this point we have our stub provider all set up, now we need to modify our production code to allow us to inject the DiscountsService dependency.

Injecting the dependency

We’ve created our stub provider but now we need to make our code ready for accepting the dependency. In other words we need to inject either the real or mock DiscountsService into the OpportunitiesService class. Notice that we inject the DiscountsService into the OpportunitiesService via the constructor. This is the dependency injection part.

public with sharing class OpportunitiesService {

   DiscountsService ds;

   public OpportunitiesService(DiscountsService ds){
      this.ds = ds;
   }   

   public Opportunity createOpportunity(Id accountId){
      Opportunity o = new Opportunity(
         Name = 'New Opportunity',
         StageName = 'New',
         Discount__c = ds.getDiscount(accountId)
      );
      insert o;
      return o;
   }

}

We’re now ready to inject either the production or a mocked DiscountsService into our OpportunitiesService.

Creating our unit test

Below is a fully functional unit test class for our new updates we’ve made. The class is testing that when we create opportunities they receive the correct discount amount based on the number of opportunities which were closed won.

Notice how the class was able to test all scenarios relatively easily without having to set up lots of test data for the DiscountsService in advance.

@isTest
public class OpportunitiesServiceTest {

   @isTest
   private static void discountShouldBeZero(){
       System.assertEquals(20, createOpportunity(0).Discount__c);
   }

   @isTest
   private static void discountShouldBeTen(){
       System.assertEquals(10, createOpportunity(15).Discount__c);
   }

   @isTest
   private static void discountShouldBeTwenty(){
       System.assertEquals(20, createOpportunity(25).Discount__c);
   }

   @isTest
   private static void discountShouldBeFifty(){
       System.assertEquals(50, createOpportunity(60).Discount__c);
   }

   private static Opportunity createOpportunity(Integer amount){
      Account a = new Account(Name = 'Test');
      insert a;
        
      DiscountsService mockDS = (DiscountsService) Test.createStub(DiscountsService.class, new MockDiscountsService(amount));
        
      Opportunity o = new OpportunitiesService(mockDS).createOpportunity(a.Id);
      return o;
   }
    
}

Each unit test above is create a new opportunity by calling the createOpportunity method. Inside that method is where we create our mock DiscountsService. Let’s have a look into that line which creates the mock:

DiscountsService mockDS = (DiscountsService) 
            Test.createStub(DiscountsService.class, 
                              new MockDiscountsService(amount));

The mock DiscountsService is created using the Test.createStub method. The method accepts two parameters:

  • System.Type parentType – this the type of the class you wish to mock.
  • System.StubProvider stubProvider – this is the stub provider which will be used to generate the mock class.

Notice that we also pass in an amount into the stub providers’ constructor to influence what the discount amount will be when we use it within the unit tests.

As the return type of Test.createStub is always an Object we also need to cast it back to the right data type before we can interact with it.

Now we have a mocked DiscountsService ready we can now inject this into our OpportunitiesService. The following line does this:

Opportunity o = new OpportunitiesService(mockDS)
                            .createOpportunity(a.Id);

Now when the createOpportunity method is called it isn’t aware that it is working with a mocked DiscountsService and will happily work with the mocked version instead.

Be aware of the limitations!

When using the stub API ensure you pay attention to the following limitations which states in the Salesforce documentation:

  • The object being mocked must be in the same namespace as the call
    to the Test.createStub() method. However, the implementation of the
    StubProvider interface can be in another namespace.
  • You can’t mock the following Apex elements.
    • Static methods (including future methods)
    • Private methods
    • Properties (getters and setters)
    • Triggers
    • Inner classes
    • System types
    • Classes that implement the Batchable interface
    • Classes that have only private constructors
  • Iterators can’t be used as return types or parameter types.

Documentation

You can find all of the documentation for the new stubbing API over at Salesforce at the following URL:

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_interface_System_StubProvider.htmhttps://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_stub_api.htm

Other options for stubbing

Stubbing isn’t entirely new to Apex as the fantastic framework from Financialforce called FFLIB also enables this and much more in combination with ApexMocks.

If you’re interesting in finding a good tutorial on how to get started then have a look at unit testing with apex-enterprise patterns and apexmocks.