Writing Unit Tests with the Apex Stub API

The Apex Stub API applies only within an Apex test context. So it cannot be used to implement DI outside of tests. For this you still need to leverage Apex interfaces.

Utilizing the APIs requires an understanding of the following:

  • Implementing the Stub Provider interface: The System.StubProvider system-provided Apex interface is effectively a callback style interface. It allows your mocking code to be informed when method calls are against classes you are mocking in your test. You can implement this interface multiple times, once per class you're mocking, or a single implementation for building sophisticated generalized mocking frameworks. The Apex Mocks open source framework from FinancialForce.com is one such framework that we will be reviewing later.
  • Dynamic Creation of Stubs for Mocking: The platform automatically creates instances of classes you wish to mock through the Test.createStub method. You do not need to create Apex interfaces to perform DI when using the Stub API. The only requirement is that you provide the platform with an implementation of the Test.StubProvider interface to callback on when methods on the created stub instance are called during test execution.

Implementing Mock classes using Test.StubProvider

Implementing mock handling code with the System.StubProvider requires only one method to be implemented as opposed to each of the methods being mocked individually. This is because this interface is a generic interface for all possible method signatures.

As you can see from the following implementation of the MockDashboard class, the handleMethodCall method on this interface passes all the information your mocking code needs in order to identify which method on which object has been called.

private class MockDashboard implements System.StubProvider {
        
    public Boolean initialiseCalled = false;
        
    public Object handleMethodCall(
        Object stubbedObject, 
        String stubbedMethodName, 
        Type returnType, 
        List<Type> listOfParamTypes, 
        List<String> listOfParamNames, 
        List<Object> listOfArgs) {

        // Record method call                
        if(stubbedMethodName == 'initialise') {
            initialiseCalled = true;
        }
        return null;
    }        
}

Once again this mock implementation implements only the minimum needed to satisfy the mocking requirements of the test being performed. It simply validates that the initialise method was called.

Note

Salesforce has provided a rich set of parameters to allow more generic implementations of this method to occur. We will review these later in this chapter through the Apex Mocks framework.

Creating dynamic stubs for mocking classes

Having implemented the System.StubProvider it is still not possible to pass an instance of the MockDashboard class to the Car class since it cannot be cast to an instance of dashboard. To make this possible the platform-provided Test.createStub method must be called. This internally creates an instance of the Dashboard class but routes method calls to the provided stub provider instead of the real code.

A convention I decided to follow in these samples is to wrap the call to this method through a createStub method of my own on the mocking class. This reduces the amount of repetition when calling it (boiler plate code) and makes it easier to use when writing the unit testing code, as you will see in the next section.

private class MockDashboard implements System.StubProvider {
        
    public Boolean initialiseCalled = false;
        
    public Object handleMethodCall( ... ) {
       ...
    }        
        
    public Dashboard createStub() {
        return (Dashboard) Test.createStub(Dashboard.class, this); 
    }
}

Mocking Examples with the Apex Stub API

After implementing the MockDashboard class, we can use it in a unit test. The resulting unit test is similar to those illustrated using Apex Interfaces:

@IsTest
private static voidwhenCarStartCalledDashboardAndEngineInitialised() {
        
    // Given
    MockDashboard mockDashboard = new MockDashboard();
    MockEngine mockEngine = new MockEngine();
        
    // When
    Car car = new Car(mockEngine.createStub(), mockDashboard.createStub());
    car.start();
        
    // Then
    System.assert(car.isRunning());        
    System.assert(mockDashboard.initialiseCalled);
    System.assert(mockEngine.startCalled);
}

Note

The sample unit test code and mocks used for this unit test are implemented in the CarTestApexStubAPIDemo class.

The main difference from the Apex interface approach is the creation of the stub instances of the classes being mocked using the applicable createStub methods.

The following code shows the handleMethodCall method in the MockEngine class used in the preceding unit test. It illustrates how a mock response can be returned for the Engine.isRunning method:

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

    // Record method call    
    if(stubbedMethodName == 'isRunning') {
        isRunningCalled = true;
        return true;    
    } else if(stubbedMethodName == 'start') {
        startCalled = true;
    }
    return null;
}

The following code shows the handleMethodCall method from the MockDisplay class (see the DashboardTestApexStubAPIDemo class for full code) and the use of the listOfArgs parameter:

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

    // Record method call and parameter values   
    if(stubbedMethodName == 'showMessage') {
        showMessageCalled = true;
        showMessageCalledWithMessage = (String) listOfArgs[2];
    }
    return null;
}        

The preceding mock handler code monitors the showMessage method being called by the Dashboard updateRPMs method. It then stores parameter values for later assertion, in addition to the fact the method itself was called.

The following unit test code leverages this to test that the correct information is passed to the display when the udpateRPMs method is called:

@IsTest
private static void whenUpdateRPMsCalledMessageIsDisplayed() {
        
    // Given
    MockDisplay mockDisplay = new MockDisplay();        
        
    // When
    Dashboard dashboard = new Dashboard(mockDisplay.createStub());
    dashboard.updateRPMs(5000);
        
    // Then
    System.assert(mockDisplay.showMessageCalled);
    System.assertEquals('RPM:5000',mockDisplay.showMessageCalledWithMessage);
}

Considerations when using the Apex Stub API

The following are some general notes and considerations for the Apex Stub API:

  • The Apex new operator still creates the real type when used in your application code. Therefore, you still need to leverage a Dependency Injection convention, such as one of them described in this chapter, to inject the alternative implementation of any dependent class you wish to mock in your unit tests.
  • Explicitly defined interfaces between the dependency boundaries of your classes are not strictly required, but might be a useful means of further enforcing SOC boundaries or for further configuration of your application.
  • Not all methods and classes can be stubbed using the Test.createStub method. Examples include inner classes and get/set accesses. Also methods returning Iterator. For a full list consult the Apex Developers Guide.
  • Static methods are also not able to be stubbed, this is by design, since these methods are not open to dependency injection. Although the Service layer design convention utilizes static methods, the actual implementation of those methods goes through the service factory to access the actual service implementation via instance methods. This can be seen in the RaceService included in the sample code.
  • Other restrictions over what can be mocked, such as private methods and constructors, might seem like an omission. However, in principle you should be testing the public behavior of your classes in your unit test only anyway. Once you mock an object method the constructor used to instantiate the object is not significant, as your mocking code determines what each method returns.
  • Keep in mind that, changes to method signatures can break the late bound references to parameters and return types made in your mock implementations. In this respect, explicit interface-based mock implementations have an advantage as a compilation error will occur if you make breaking changes. Mocking frameworks can help mitigate this risk as we will discuss in the next section.

Using Apex Stub API with Mocking Frameworks

Implementing mock classes for small code bases or as an aid to get a better understanding of the concept of unit testing, as we have done in this chapter is fine. However, it can quickly become onerous and a maintenance overhead for larger code bases. The Apex Stub API was designed to handle any class shape for a reason.

The stubbedObject parameter can be used to determine from what type the method being called belongs. This allows for the creation of a single System.StubProvider implementation that can implement more advanced services that can be leveraged by unit tests without the need to write their own mock classes over and over. Playback of preconfigured responses to mocked methods and recording of each method call it receives for later assertion is an example of this.

The concept of a mocking framework is not new. There are many well established frameworks such as the popular Mockitio framework for Java or Jasmine for JavaScript/NodeJS applications. However, mocking is a relatively new concept and capability in Apex, so the space is still emerging for Apex-based mocking frameworks.

FinancialForce.com has published an open source mocking framework known as ApexMocks. While it predates the arrival of the Apex Stub API it has been updated to work with this API. The advantage is that the code generator used to create the mock classes for use with Apex Mocks is no longer required; nor is the explicit use of Apex interfaces.

In the remainder of this chapter we will review the benefits of a mocking framework through the use of the ApexMocks framework (now included in the sample code of this chapter). We apply it first to our sample Car class model and then to unit test scenarios from the FormulaForce application. A full walkthrough is worthy of an entire book in my view. You can read more about ApexMocks through the various blog and documentation links on its READ ME file, https://github.com/financialforcedev/fflib-apex-mocks/blob/master/README.md.

Note

Credit should be given to Paul Hardaker for the original conception and creation of ApexMocks. As an experienced Java developer coming to the platform and used to using Mockito he was quickly driven to find out whether a similar unit testing facility could be created on the platform. Thus, ApexMocks inherits much of its API design from Mockito. Since then the framework has gone from strength to strength and received several other submissions and support from the community including presentations at Dreamforce. Much of what I know about the area of unit testing is also due to Paul's tutoring. Thank you!

Understanding how ApexMocks works

The whenCarStartCalledDashboardAndEngineInitialised unit test method in the CarTestApexMocksDemo test class uses the ApexMocks library. The structure of this unit test is similar to those we have looked at so far in this chapter. It creates the mocks, runs the method to test, and asserts the results.

The big difference is that you do not need to write any code for classes you wish to mock. Instead you use the ApexMocks framework to configure your mock responses and to assert which methods where recorded. Then through its integration with the Apex Stub API it creates a stub that wraps itself around any class you wish to mock.

The main class within the ApexMocks framework is fflib_ApexMocks. This class implements the System.StubProvider interface for you. It automatically echoes any mock responses to mocked methods called and records that they got called and with what parameters. This applies to all public methods on the classes you ask it to mock for you.

Much of the skill in using ApexMocks is knowing how to configure the mocked responses it gives when mocked methods are called and how to write matching logic to assert not only when a method was called, but how many times and with what parameters. This later facility is incredibly powerful and opens up some sophisticated options for verifying the behavior of your code.

The following code initializes the ApexMocks framework (every test method needs this):

@IsTest
private static voidwhenCarStartCalledDashboardAndEngineInitialised() {
        
   fflib_ApexMocks mocks = new fflib_ApexMocks();

Next create the mock implementations that you need:

   // Given
   Dashboard mockDashboard = (Dashboard) mocks.factory(Dashboard.class);
   Engine mockEngine = (Engine) mocks.factory(Engine.class);

The following code replaces the need for you to write mock methods that return test values during the execution of code being tested. In ApexMock terms this configuration code is known as a stubbing code. You must surround stubbing code in startStubbing and stopStubbing method calls as shown here:

   mocks.startStubbing();
   mocks.when(mockEngine.isRunning()).thenReturn(true);
   mocks.stopStubbing();

Tip

The syntax gets a little tricky to understand at this stage, but you will get used to it, I promise! As I stated earlier, much of the inspiration for the ApexMocks API is taken from the Java Mockitio library. So much so that articles and documentation around the Internet should also aid you in learning the API, as well as the documentation provided on the GitHub repo.

Your eyes are not deceiving you; the preceding code is calling the method you're attempting to mock a response for. First of all, this is a compile time reference, so it is much safer than a late bound string reference we used when using the Apex Stub API raw.

Secondly, because the mockEngine points to a dynamically generated stub, it is not calling the real implementation of this method. Instead it is registering in the framework that the next method call, thenReturn, should store the given value as a response to this method. This will be given back at a later point in the test when that mocked method is called again, this time in the test case context. In this case when the Car.isRunning method calls engine.isRunning (refer to the Car code if you need to refresh your memory as to what the implementation does) it returns the value true.

The next part of the unit test, the actual code being tested, is more familiar:

   // When        
   Car car = new Car(mockEngine, mockDashboard);
   car.start();

Asserting that the Dashboard.initialise and Engine.start methods have been called, as well as the value returned by the Car.isRunning is as expected, is also a little different from when calling the hand written mock class methods:

   // Then
   System.assertEquals(true, car.isRunning());
   ((Dashboard) mocks.verify(mockDashboard, 1)).initialise();
   ((Engine) mocks.verify(mockEngine, 1)).start();        }

Note

Due to the explicit casting requirements of Apex, the last two lines do not quite match what you would see in an equivalent Java Mockito example. This can make the syntax harder to read. In Java Mockito it would look like:

mocks.verify(mockDashboard, 1).initialise();
mocks.verify(mockEngine, 1).start();

The assertion code is a mixture of traditional System.assert and calls to the fflib_ApexMocks.verify method, followed by a call to the methods being mocked. Once again this compiler-checked reference is preferable to late bound string references.

The verify method takes an instance of the mocked object and number. This number allows you to assert that the method was called once and only once. Once the verify method exits and the mocked method is called, the expected result passed to the verify method is checked. If it is not met, either because the method was not called, or it was called more than once, the framework actually has the mocked method use System.assert to halt the test with an exception message describing why.

Another example is the whenUpdateRPMsCalledMessageIsDisplayed test method in the DashboardTestApexMocksDemo class. This utilizes a more advanced form of asserting that the desired mocked method was called. This approach not only validates that it was called but with specific parameter values:

@IsTest
private static void whenUpdateRPMsCalledMessageIsDisplayed() {
        
    fflib_ApexMocks mocks = new fflib_ApexMocks();
        
    // Given
    Display mockDisplay = (Display) mocks.factory(Display.class);
        
    // When
    Dashboard dashboard = new Dashboard(mockDisplay);
    dashboard.updateRPMs(5000);
        
    // Then
    ((Display) mocks.verify(mockDisplay, 1)).showMessage(10, 10, 'RPM:5000'),        
}

The preceding use of the verify method is no different from the previous test. However, the following mocked method call illustrates a very cool feature of ApexMocks verification logic. The parameters passed in the call to the mocked method are used by the framework to compare with those recorded during prior execution of the method during the preceding test. If any differences are detected an assertion is thrown and the test is stopped.

Tip

Some of the preceding examples include a System.assert method call inline. Other unit tests you write may not require use of this statement as they solely assert the correct dependent methods have been called through the verify method. This can lead to a false positive in the Salesforce Security Review scanner, since the verify method is not recognized as a method asserting quality. If this happens you can add a comment neutral assert to pacify the scanner, or record the reason why in the false positive document you attach to the review.

ApexMocks Matchers

As you can see there are two key parts to using ApexMocks—setting up the mocked responses through stubbing, and asserting the behavior through verifying. Both these facilities support the ability customize what mocked values are returned or what parameters are matched during verify.

For example, if the code you're unit testing calls a mocked method several times with different parameters, you can vary the mocked value returned using a convention known as a matcher during the stubbing phase of your test code.

Tip

It is also possible to configure the framework's throwing of exceptions when mocked methods are called, if that is the desired test case you want to cover.

When asserting behavior, if you wish to check that a mocked method is called twice—once with a certain set of parameter values and a second time with a different set of parameters—then matchers can be used to express this desired outcome.

Matchers is an advanced feature, but well worth further investment of your time to learn.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.160.131