A super fast, lightweight, apex mocking and assertion library.
The L3 library provides a Salesforce testing utility built around the Stub API, enabling developers to mock Apex classes and methods and asserting their invocations with specific parameters and call counts.
The api was inspired by jest and allows for your apex tests to behave like your lwc tests.
Table of Contents
- lib/src: Contains the source code of the testing utility library.
- lib/test: Contains test classes for the library.
- demo/: Contains a demo showcasing the L3 library in action.
To levergage this library you will need to provide a way for the mock classes to be injected from your tests to the classes that need to be mocked.
This can be as simple as exposing a @TestVisible
instance in your classes and having a static
method construct instances of your class and returning the mock
Example
public with sharing class AccountsSelector {
@TestVisible
private static AccountsSelector instance;
public static AccountsSelector newInstance() {
if (instance == null) {
instance = new AccountsSelector();
}
return instance;
}
public List<Account> getAccountsById(Set<Id> accountIds) {
// code
}
}
When you have a mechanism available to inject the mock you can begin using it.
@IsTest
private class CaseServiceTest {
@IsTest
private static void calculatesHighPriorityFromAccountValue() {
// given
Account testAccount = new Account(
Id = TestUtils.generateId(Account.SObjectType),
AnnualRevenue = 1000000
);
Case testCase = new Case(
AccountId = testAccount.Id
);
// mock setup
MockClass mockAccountSelector = new MockClass();
MockMethod selectByAccountId = new MockMethod('selectByAccountId', new List<Object> { new List<Account> {testAccount} });
mockAccountSelector.addMockMethod(selectByAccountId);
AccountsSelector.instance = (AccountsSelector) Test.createStub(AccountsSelector.class, mockAccountSelector);
// when
CaseService.newInstance(new List<Case>{testCase}).calculatePriority();
// then
Assert.areEqual('High', testCase.Priority, 'Expected High Priority');
// mock assertions
Assert.areEqual(1, selectByAccountId.getTimesCalled(), 'Expected AccountsSelector.selectByAccountId to have been called once');
Assert.areEqual(new Set<Id>{ testAccount.Id }, selectByAccountId.getNthCalledWith(1)[0], 'Expected AccountsSelector.selectByAccountId to have been called with the test account id');
}
}
A MockClass
represents a class to be mocked in a unit test. An instance of MockClass
holds a list of MockMethods
that belong to the class.
public MockMethod()
MockClass mockAccountSelector = new MockClass();
Adds a MockMethod
to the MockClass
public void addMockMethod(MockMethod method)
- method: Adds a
MockMethod
to the instance that is expected to be called in a unit test.
MockClass mockAccountsSelector = new MockClass();
MockMethod mockSelectById = new MockMethod('selectById', mockListOfAccountsToReturn);
mockAccountsSelector.add(mockSelectById);
Called by the Stub API when invoking mocks in unit tests. Not very useful to call directly.
public Object handleMethodCall
- 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.
This parameter stores a list of MockMethodCall
objects representing each call to the mock class. This is useful for
asserting the order of method calls in a unit test and the parameters passed to each method.
A MockMethod
represents a method to be mocked in a unit test. An instance of MockMethod
holds all the parameters and returns the mocked values when invoked.
public MockMethod(String methodName, List<Object> mockValues)
- methodName: The name of the method to be mocked
- mockValues: The list of values to return when mocked. Each item in the list represents a return value for an invocation. If a method is called multiple times, then the list should contain multiple values for each invocation.
MockClass mockAccountsSelector = new MockClass();
List<Account> accountsToReturnOnFirstInvocation = new List<Account>{ /* specific accounts */ };
List<Account> accountsToReturnOnSecondInvocation = new List<Account> { /* other specific accounts */ }
List<Object> returnValues = new List<Object> { accountsToReturnOnFirstInvocation, accountsToReturnOnSecondInvocation };
MockMethod mockSelectById = new MockMethod('selectById', returnValues);
mockAccountsSelector.addMockMethod(mockSelectById);
Defining exceptions: If you want to throw an exception when the method is called, you can define an exception as a return value. The exception should be an instance of Exception
.
MockClass mockAccountsSelector = new MockClass();
List<Account> accountsToReturnOnFirstInvocation = new List<Account>{ // specific accounts };
MyCustomException customException = new MyCustomException('Throw me'); // Exception to throw second time method is called
List<Object> returnValues = new List<Object> { accountsToReturnOnFirstInvocation, customException };
MockMethod mockSelectById = new MockMethod('selectById', returnValues);
mockAccountsSelector.addMockMethod(mockSelectById);
Utility method used by MockClass
to confirm mocks aren't overwritten.
public String getName()
MockMethod mockSelectById = new MockMethod('selectById', mockListOfAccountsToReturn);
Assert.areEqual('selectById', mockSelectById.getName());
Returns the number of times the method was invoked in a unit test
public Integer getTimesCalled
MockClass mockAccountsSelector = new MockClass();
List<Account> accountsToReturnOnFirstInvocation = new List<Account>{ // specific accounts };
List<Account> accountsToReturnOnSecondInvocation = new List<Account> { // other specific accounts }
List<Object> returnValues = new List<Object> { accountsToReturnOnFirstInvocation, accountsToReturnOnSecondInvocation };
MockMethod mockSelectById = new MockMethod('selectById', returnValues);
mockAccountsSelector.addMockMethod(mockSelectById);
// Call method under test
Assert.areEqual(2, mockSelectById.getTimesCalled());
Returns the list of parameters the method was called with for a specific nth invocation
public List<Object> getNthCalledWith(Integer n)
- n: The nth invocation to return the invocation parameters for
MockClass mockAccountsSelector = new MockClass();
Id account1Id = TestUtils.generateId(Account.SObjectType);
Id account2Id = TestUtils.generateId(Account.SObjectType);
List<Account> accountsToReturnOnFirstInvocation = new List<Account>{ new Account(Id = account1Id) };
List<Account> accountsToReturnOnSecondInvocation = new List<Account> { new Account(Id = account2Id) };
List<Object> returnValues = new List<Object> { accountsToReturnOnFirstInvocation, accountsToReturnOnSecondInvocation };
MockMethod mockSelectById = new MockMethod('selectById', returnValues);
mockAccountsSelector.addMockMethod(mockSelectById);
// Call method under test
List<Object> 1stInvocationParameters = mockSelectById.getNthCalledWith(1);
List<Object> 2ndInvocationParameters = mockSelectById.getNthCalledWith(2);
Assert.areEqual(account1Id, 1stInvocationParameters[0]);
Assert.areEqual(account2Id, 2ndInvocationParameters[0]);
public Object stubbedObject;
public String stubbedMethodName;
public Type returnType;
public List<Type> listOfParamTypes;
public List<String> listOfParamNames;
public List<Object> listOfArgs;
- Static methods cannot be mocked by the stub api and thus affects this library as well.
- When mocking
void
methods, make sure you specifynull
as the return type. The stub api expects a return value regardless of if the method actually returns one. - Unit tests are not a substitue to integration tests. Integration tests are still needed to make sure your application can run at scale and adhere to all the governor limits.