Newer Version Available
Build a Mocking Framework with the Stub API
You can define the behavior of stub objects, which are created at runtime as anonymous subclasses of Apex classes. The stub API comprises the System.StubProvider interface and the System.Test.createStub() method.
Let’s look at an example to illustrate how the stub API works. This example isn’t meant to demonstrate the wide range of possible uses for mocking frameworks. It’s intentionally simple to focus on the mechanics of using the Apex stub API.
Let’s say we want to test the formatting method in the following class.
1public class DateFormatter {
2 // Method to test
3 public String getFormattedDate(DateHelper helper) {
4 return 'Today\'s date is ' + helper.getTodaysDate();
5 }
6}Usually, when we invoke this method, we pass in a helper class that has a method that returns today’s date.
1public class DateHelper {
2 // Method to stub
3 public String getTodaysDate() {
4 return Date.today().format();
5 }
6}The following code invokes the method.
1DateFormatter df = new DateFormatter();
2DateHelper dh = new DateHelper();
3String dateStr = df.getFormattedDate(dh);For testing, we want to isolate the getFormattedDate() method to make sure that the formatting is working properly. The return value of the getTodaysDate() method normally varies based on the day. However, in this case, we want to return a constant, predictable value to isolate our testing to the formatting. Rather than writing a “fake” version of the class, where the method returns a constant value, we create a stub version of the class. The stub object is created dynamically at runtime, and we can specify the “stubbed” behavior of its method.
- Define the behavior of the stub class by implementing the System.StubProvider interface.
- Instantiate a stub object by using the System.Test.createStub() method.
- Invoke the relevant method of the stub object from within a test class.
Implement the StubProvider Interface
Here’s an implementation of the StubProvider interface.
1@isTest
2public class MockProvider implements System.StubProvider {
3
4 public Object handleMethodCall(Object stubbedObject, String stubbedMethodName,
5 Type returnType, List<Type> listOfParamTypes, List<String> listOfParamNames,
6 List<Object> listOfArgs) {
7
8 // The following debug statements show an example of logging
9 // the invocation of a mocked method.
10
11 // You can use the method name and return type to determine which method was called.
12 System.debug('Name of stubbed method: ' + stubbedMethodName);
13 System.debug('Return type of stubbed method: ' + returnType.getName());
14
15 // You can also use the parameter names and types to determine which method
16 // was called.
17 for (integer i =0; i < listOfParamNames.size(); i++) {
18 System.debug('parameter name: ' + listOfParamNames.get(i));
19 System.debug(' parameter type: ' + listOfParamTypes.get(i).getName());
20 }
21
22 // This shows the actual parameter values passed into the stubbed method at runtime.
23 System.debug('number of parameters passed into the mocked call: ' +
24 listOfArgs.size());
25 System.debug('parameter(s) sent into the mocked call: ' + listOfArgs);
26
27 // This is a very simple mock provider that returns a hard-coded value
28 // based on the return type of the invoked.
29 if (returnType.getName() == 'String')
30 return '8/8/2016';
31 else
32 return null;
33 }
34}StubProvider is a callback interface. It specifies a single method that requires implementing: handleMethodCall(). When a stubbed method is called, handleMethodCall() is called. You define the behavior of the stubbed class in this method. The method has the following parameters.
- stubbedObject: The stubbed object
- stubbedMethodName: The name of the invoked method
- returnType: The return type of the invoked method
- listOfParamTypes: A list of the parameter types of the invoked method
- listOfParamNames: A list of the parameter names of the invoked method
- listOfArgs: The actual argument values passed into this method at runtime
You can use these parameters to determine which method of your class was called, and then you can define the behavior for each method. In this case, we check the return type of the method to identify it and return a hard-coded value.
Instantiate a Stub Version of the Class
The next step is to instantiate a stub version of the class. The following utility class returns a stub object that you can use as a mock.
1public class MockUtil {
2 private MockUtil(){}
3
4 public static MockProvider getInstance() {
5 return new MockProvider();
6 }
7
8 public static Object createMock(Type typeToMock) {
9 // Invoke the stub API and pass it our mock provider to create a
10 // mock class of typeToMock.
11 return Test.createStub(typeToMock, MockUtil.getInstance());
12 }
13}This class contains the method createMock(), which invokes the Test.createStub() method. The createStub() method takes an Apex class type and an instance of the StubProvider interface that we created previously. It returns a stub object that we can use in testing.
Invoke the Stub Method
Finally, we invoke the relevant method of the stub class from within a test class.
1@isTest
2public class DateFormatterTest {
3 @isTest
4 public static void testGetFormattedDate() {
5 // Create a mock version of the DateHelper class.
6 DateHelper mockDH = (DateHelper)MockUtil.createMock(DateHelper.class);
7 DateFormatter df = new DateFormatter();
8
9 // Use the mocked object in the test.
10 System.assertEquals('Today\'s date is 8/8/2016', df.getFormattedDate(mockDH));
11 }
12}In this test, we call the createMock() method to create a stub version of the DateHelper class. We can then invoke the getTodaysDate() method on the stub object, which returns our hard-coded date. Using the hard-coded date allows us to test the behavior of the getFormattedDate() method in isolation.
Apex Stub API Limitations
Keep the following limitations in mind when working with the Apex stub API.
- 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.