Well color me surprised, my last article on mocking in apex was, well, a little more popular than I thought it would be. I was happy that so may people enjoyed it, but it got me thinking, “Bob, you handsome and brilliant, yet refreshingly humble man, you can do more for your fans!” After an obligatory back patting, I set off to add more features. This will build entirely off of the last article. In fact, you are doing yourself a disservice if you don’t read it post haste.

We start with a change to add parameter matcher, a handy feature for times when you are not 100% sure the value that is going into you method. All we need is an enum.

public enum Matcher{
    anyVal
}

This is the bare minimum to work while still providing room for enhancement. The only thing in this enum is the value anyVal. What we are saying is when this is used, accept any value. Next I modified my interface, services and test to give the method getAuthCode a Datetime parameter. The OrderService will pass in Datetime.now(), making this almost impossible to mock without matchers. There also needs to be a change in Mock maker to handle this new condition.

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++){
        if(topCommand.parameters[i] != null && topCommand.parameters[i] instanceOf Matcher){
            continue;
        }
        System.assertEquals(topCommand.parameters[i], parameters[i]);
    }
    return topCommand.returnValue;
}

It isn’t much different than it was two weeks ago, except now it checks if the parameter is a matcher. If it is, and since there is only one possible value it could be, we just skip any check on the parameter and continue to the next. Now we can change our test to work with this new code.

public static testmethod void testProcessOrderPayment(){
        beforeTest();
        MockMaker.addCall('getAuthCode', new List<Object>{Matcher.anyVal}, '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);
    }

Now our call to getAuthCode will work no matter what we pass in. A quick test run shows both tests passing… but that isn’t right, now is it? We only changed one test yet both still pass. It isn’t enough to simply check the parameters, we need to check the number of them as well. Otherwise our loop could give false positives like in this case, or throw exceptions. Let’s put a halt to that right now.

public static Object getCall(String methodName, List<Object> parameters){
    Command topCommand = commands.get(methodName).remove(0);
    System.assertEquals(topCommand.parameters.size(), parameters.size());
    for(Integer i = 0; i < topCommand.parameters.size(); i++){
        if(topCommand.parameters[i] instanceOf Matcher){
            continue;
        }
        System.assertEquals(topCommand.parameters[i], parameters[i]);
    }
    return topCommand.returnValue;
}

One extra line asserts that the number of parameters passed in is the same as those expected.

There is another case we are still not testing for. If you remember last time, I used mocks to return a purposeful bad value to test out some error handling. What if we wanted to test what happens when the service we are mocking throws an exception itself? We need some more code for this one but before that, we need an exception to throw.

public class PaymentTimeoutException extends Exception{}

Phew, that was a lot of work, but I soldier on. Next is some code in our service to expect such an exception.

public static String processOrderPayment(){
    PaymentInterface payment = ServiceFactory.getPaymentInterface();

    String authCode = payment.getAuthCode(Date.today());
    if(authCode == null){
        throw new BadAuthException();
    }

    try{
        return payment.processPayment(authCode);
    } catch(PaymentTimeoutException e) {
        return 'Failed to process. Reason: ' + e.getMessage();
    }
}

Now we need to get MockMaker to throw the exception at the right time.

public static Object getCall(String methodName, List<Object> parameters){
    Command topCommand = commands.get(methodName).remove(0);
    System.assertEquals(topCommand.parameters.size(), parameters.size());
    for(Integer i = 0; i < topCommand.parameters.size(); i++){
        if(topCommand.parameters[i] != null && topCommand.parameters[i] instanceOf Matcher){
            continue;
        }
        System.assertEquals(topCommand.parameters[i], parameters[i]);
    }

    if(topCommand.returnValue != null && topCommand.returnValue instanceOf Exception){
        throw (Exception)topCommand.returnValue;
    }

    return topCommand.returnValue;
}

This getCall method keeps getting all of the attention. Another conditional now checks if the return value is actually an exception and if so, it throws it. The only thing left is the test.

public static testmethod void testProcessOrderPaymentTimeout(){
    beforeTest();
    MockMaker.addCall('getAuthCode', new List<Object>{Matcher.anyVal}, '123456');
    MockMaker.addCall('processPayment', new List<Object>{'123456'},
        New PaymentTimeoutException('Payment timeout'));

    String retVal = OrderService.processOrderPayment();
    System.assertEquals('Failed to process. Reason: Payment timeout', retVal);
}

The only real difference here is that the addCall for processPayment has an exception passed into it and the end assert reflects the caught exception.

With just a bit of work, MockMaker now checks the quantity of parameters, uses matchers to check for values that might not be known when the test is started, and throws exceptions on command. While not on par with some other language’s mock suits, it can go a long way to making life easier for a developer.