TL; DR;
Standardising mocking throughout your code base in the simplest way possible without mixing test-only and production code.
See on GitHub
The Problem
Mocking in (Apex) unit tests is very useful. There are, however, a few challenges to it. How exactly do you replace the “real” object with a stubbed version without adding a lot of extra complexity?
How We Mock
If (all) objects you need to mock are passed as arguments to a tested method it’s quite simple. That is rarely the case though. We need to test more complex units most of the time.
Using System.isRunningTest()
to control whether to initialise a real class or a substitute can be tempting, but it’s not usually a good idea. It adds extra complexity to your code that is only relevant in tests. It just gets in the way of your code telling a clear story.
In some cases we have static factory methods that we can adjust. That’s usually quite good, especially combined with the private access modifier and @TestVisible
annotation. We can modify non-static private members that way too. This way we ensure no “live” code can do anything unintended but the special Test context can inject some modified behaviour.
@TestVisible
private static CampaignEmailReminderService mockInstance;
public static CampaignEmailReminderService newInstance(CampaignEmailReminderSetting setting) {
if (mockInstance != null) {
return mockInstance;
}
return new CampaignEmailReminderService(setting);
}
How We Should Mock
Good, but it technically still is a special case for Test only in a productive piece of code. A more sophisticated factory is needed for this to be super-clean. A great example is the Application class from ApexCommon framework. Here it is in action in the sample repository (don’t want to re-post the code, the copyright notice is too long).
Since the Application class is where all the initialisation takes place, we just register the Mocks with it at the start of the Test Method and it decides whether to serve the Mock or the “Real Deal”. None of the classes need to have any provisions for testing.
Really, give it the time if you’ve not looked into “fflib” before. I was quite late to the party myself and surprise surprise… it’s awesome!
Making It Simpler
What if you aren’t using the Enterprise Patterns though? It’s easy enough to replicate parts of it and adjust to your particular needs. You can gradually adjust your code base and then switch when ready (or not). In our project we are adopting these patterns slowly, but we aren’t using Domain classes and not everything is really an implementation of an Interface.
A simplified ClassFactory is serving us well enough instead. It’s a very stripped down version of the same Apex Common package approach. Not distinguishing between Selectors or Services though. Just a map of Type to Object for the Mocks and a generic newInstance method.
public inherited sharing class ClassFactory {
private static Map<Type, Object> typeToMockImplementation = new Map<Type, Object>();
public static Object newInstance(Type requestedType) {
if (typeToMockImplementation.containsKey(requestedType)) {
return typeToMockImplementation.get(requestedType);
}
return requestedType.newInstance();
}
@TestVisible
private static void setMock(Type mockedType, Object mockImplementation) {
typeToMockImplementation.put(mockedType, mockImplementation);
}
}
The ClassFactory’s only job is to create new instances of Types passed in or return pre-registered mock versions instead. It will work for any Class as long as it provides a public empty constructor.
Classes don’t need to call ClassFactory.newIstance()
directly. It wouldn’t read so well. The intention is for “participating” classes to wrap this method in their own newInstance factory method. Which is very much the same as in the Apex Common library examples.
public with sharing class AccountService {
public static AccountService newInstance() {
return (AccountService) ClassFactory.newInstance(AccountService.class);
}
…
Finally an Example
Not a complete example really, but I think it’s enough to show how the productive class doesn’t really need to change anything to “get mocked”.
private class ServiceClass {
private static AccountSelector accounts = AccountSelector.newInstance();
…
public void someMethod(Set<Id> accountIds) {
List<Account> accs = accounts.selectById(accountIds);
doSomeWorkOn(accs);
…
}
Inside a test method we tell the Class Factory to replace AccountSelector with an instance of FlexibleMock. If you are wondering what that is, check out my previous post.
private class ServiceClassTest {
@IsTest
void testSomeMethod() {
Map<Id, Account> testAccounts = TestFactory.makeAccounts(3);
FlexibleMock mockAccountSelector = new FlexibleMock()
.mockMethodCall(
selectById,
new List<Object>{ testAccounts.keySet() },
new List<Account>{ testAccounts.values() }
)
ClassFactory.setMock(AccountSelector.class, (AccountService) Test.createStub(AccountSelector.class, mockAccountSelector));
Test.startTest();
new ServiceClass().someMethod(testAccounts.keySet());
…
}
What’s Next
I’d be interested to hear what you think about this approach. It’s meant to be an highly achievable early step on your journey of making tests better. I know many orgs need it.
It’s all nice and fast creating records in memory and injecting them in your methods, but what if those methods are supposed to do DML. You can’t really insert a record full of fake references. In my next post I look at exactly that