Mock Mocks in Apex
I love mocking frameworks. I love the ability to isolate a unit of code so I can make smaller, more coherent tests. I have grown accustom to them during my time with java and I would love to use them with apex. The problem is that mocking frameworks rely heavily on robust reflection, a feature apex sorrily lacks.
Sorry. Lost the happy, but the happy’s back!
While we cannot yet simply tell a method to generate a mock class, maybe we can make an approximation. A lightweight class that can be substituted for the real thing when testing a class that relies on it could work in many cases. How to get the mock class in there though, that is the question. While Apex is missing most of the necessary reflection, it is not missing all of it. Enter a little known blog, by a man called Jesse Altman, that goes into dependency injection with Apex. That, my friends, is the foundation of what I will go into here. If you didn’t click the link above, and I don’t know why you wouldn’t have, read the article on dependency injection right now. Seriously, if you go on without reading it I will be disappointed in you.
Are you done? And no lying, I will know. So, I have my classes and custom settings pretty much the same as the one from Jesse’s blog post and will be building from there. The best place to start with a blog post on mocks and unit tests is probably a unit test. The unit test will initially test the the classes as they are.
public class OrderService{
public static String processOrderPayment(){
PaymentInterface payment = ServiceFactory.getPaymentInterface();
return payment.processPayment();
}
}
@isTest
public class OrderServiceTest{
public static testmethod void testProcessOrderPayment(){
insert new Inheritence_Settings__c(
Payment_Service__c = 'PaypalPaymentService'
);
String retVal = OrderService.processOrderPayment();
System.assertEquals('Paypal successfully processed your payment!', retVal);
}
}
My service just just calls processPayment on a PaymentInterface, nothing special there. The test inserts a custom setting first, grabs the return value from processOrderPayment and verifies the result. Not too much going on here, certainly not deserving a post. Yet. Lets spice this test up.
@isTest
public class OrderServiceTest{
public static testmethod void testProcessOrderPayment(){
insert new Inheritence_Settings__c(
Payment_Service__c = 'OrderServiceTest.MockPaymentService'
);
String retVal = OrderService.processOrderPayment();
System.assertEquals('Mock service successfully processed your payment!', retVal);
}
public class MockPaymentService implements PaymentInterface{
public String processPayment(){
return 'Mock service successfully processed your payment!';
}
}
}
Look at that, injecting an inner class from within the test worked just fine too. The problem is that this is a trivial example. Why bother mocking? Let’s make this a bit more complex.
public class OrderService{
public static String processOrderPayment(){
PaymentInterface payment = ServiceFactory.getPaymentInterface();
String authCode = payment.getAuthCode();
if(authCode == null){
throw new BadAuthException();
}
return payment.processPayment(authCode);
}
}
@isTest
public class OrderServiceTest{
public static testmethod void testProcessOrderPayment(){
insert new Inheritence_Settings__c(
Payment_Service__c = 'OrderServiceTest.MockPaymentService'
);
String retVal = OrderService.processOrderPayment();
System.assertEquals('Mock service successfully processed your payment!', retVal);
}
public class MockPaymentService implements PaymentInterface{
public String processPayment(String authCode){
System.assertEquals('123456', authCode);
return 'Mock service successfully processed your payment!';
}
public String getAuthCode(){
return '123456';
}
}
}
I have changed the interface to have the method getAuthCode and added a string parameter to processPayment. The service now checks for a non null authCode and throws an exception if it is. Our mock has had its methods changed accordingly and now, process payment verifies the parameter passed to it. This is closer to our mocks, but now we have a branch we can only test by adding another mock. Inconvenient indeed. One last change, one last push over the finish line. A new class will get us where we need to go.
public abstract class MockMaker{
private static Map<String, List<Command>> commands = new Map<String, List<Command>>();
public static void addCall(String methodName, List<Object> parameters, Object returnValue){
if(!commands.containsKey(methodName)){
commands.put(methodName, new List<Command>());
}
commands.get(methodName).add(new Command(parameters, returnValue));
}
public static Object getCall(String methodName, List<Object> parameters){
Command topCommand = commands.get(methodName).remove(0);
for(Integer i = 0; i < topCommand.parameters.size(); i++){
System.assertEquals(topCommand.parameters[i], parameters[i]);
}
return topCommand.returnValue;
}
private class Command{
public List<Object> parameters {get; set;}
public Object returnValue {get; set;}
public Command(List<Object> parameters, Object returnValue){
this.parameters = parameters;
this.returnValue = returnValue;
}
}
}
This hefty class will let us reuse the same mocks multiple times. I wont break this down line for line but I will summarize it. The command object holds parameters to verify against and return values to… return. The method add call adds the call to a map containing a list of command objects, keyed to the method name being mocked. The calls to a particular method must be mocked in the order they will be run. The method getCall gets the command for a method, verifies the parameters are the same and returns the stored return value. What does this look like in practice? Ask, and ye shall receive.
@isTest
public class OrderServiceTest{
public static void beforeTest(){
insert new Inheritence_Settings__c(
Payment_Service__c = 'OrderServiceTest.MockPaymentService'
);
}
public static testmethod void testProcessOrderPayment(){
beforeTest();
MockMaker.addCall('getAuthCode', new List<Object>(), '123456');
MockMaker.addCall('processPayment', new List<Object>{'123456'},
'Mock service successfully processed your payment!');
String retVal = OrderService.processOrderPayment();
System.assertEquals('Mock service successfully processed your payment!', retVal);
}
public static testmethod void testProcessOrderPaymentBadAuth(){
beforeTest();
MockMaker.addCall('getAuthCode', new List<Object>(), null);
try{
String retVal = OrderService.processOrderPayment();
System.assert(false, 'Exception not thrown');
} catch (BadAuthException e){
//Expected
}
}
public class MockPaymentService extends MockMaker implements PaymentInterface{
public String processPayment(String authCode){
return (String)getCall('processPayment', new List<Object>{authCode});
}
public String getAuthCode(){
return (String)getCall('getAuthCode', new List<Object>());
}
}
}
The mock now extends MockMaker and does nothing more than wrap different calls to getCall. The test methods (yes, now there are two) mock out the calls to the payment service with MockMaker.addCall. We can now use the single mock, injected into the OrderService via the factory, to run multiple tests. This isn’t perfect but for services with call-outs or just very heavy methods, this can be a real help.