Real Unit Tests using ApexMocks

Almost a year ago, I wrote an article on Proper Unit Test Structure in Apex. In that article I state:

A unit test is typically described as:

a method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine if they are fit for use.

In Salesforce, unit tests are really much more similar to integration tests.

Integration testing is the phase in software testing in which individual software modules are combined and tested as a group.

The reason I say this is due to the fact that Salesforce currently lacks a good mock framework.

This is all still true, Salesforce natively still doesn’t support proper unit testing. However, Paul Hardaker has created the ApexMocks framework which provides Mockito like functionality to Apex. This is going to provide huge boosts in unit test speed, ultimately helping every Apex developer move closer to a true Test Driven Development (TDD) or Behavior Driven Development (BDD) process.

The general idea behind mock objects is that it gives you the ability to tell classes what to expect from other classes. This makes your unit tests completely encapsulated from its dependencies. The entire concept relies on the idea of Dependency Injection, or simply, the idea that you pass dependencies into a class rather than the class instantiating them. So, why would we want to do this? Let’s take a look at an example.

Why do we need this?

Below you will find the traditional way of writing unit tests. In this scenario, we are unit testing a controller, specifically the calculateValues() method. Let’s take a look:

MyController

[java]
public class MyController{
private MyInterface myImplementation;
public Integer valueOne{get;set;}
public Integer valueTwo{get;set;}

public MyController(MyInterface myImplementation){
this.myImplementation = myImplementation;
}

public Integer calculatedTotalValue(){
return myImplementation.calculateValues(valueOne, valueTwo);
}
}
[/java]

MyControllerTest

[java]
@isTest
public class MyControllerTest{
public static testMethod void testCalculateValues(){
Test.startTest();
MyController controller = new MyController(new MyService());
controller.valueOne = 5;
controller.valueTwo = 3;
Integer totalValue = controller.calculatedTotalValue();
Test.stopTest();

System.assertEquals(8, totalValue, ‘The service should return 8’);
}
}
[/java]

MyInterface

[java]
public interface MyInterface{
Integer calculateValues(Integer valueOne, Integer valueTwo);
}
[/java]

MyService

[java]
public class MyService implements MyInterface{
public Integer calculateValues(Integer valueOne, Integer valueTwo){
return valueOne + valueTwo;
}
}
[/java]

This all looks great right? Well, let me ask you a question. What exactly does MyControllerTest.testCalculateValues() really test? You may be inclined to say it tests MyController.calculateValues(), and you are correct, but that isn’t the only thing it tests. It also tests MyService.calculateValues() and I can prove it. Say for instance, a second developer comes along and changes MyService to:

[java]
public class MyService implements MyInterface{
public Integer calculateValues(Integer valueOne, Integer valueTwo){
return valueOne – valueTwo;
}
}
[/java]

When you run the unit tests, the MyControllerTest.testCalculateValues() now fails.

System.AssertException: Assertion Failed: The service should return 8: Expected: 8, Actual: 2

This is what I would call a false positive. Nothing changed in MyController, so why would the unit test break? The issue is that the MyControllerTest.testCalculateValues() is completely dependent on MyInterface’s implementation. This should never happen in true unit tests. A unit test should only fail when that specific unit is experiencing an error. That is not the case here.

Another issue is that the second developer may not actually see this issue unless they run through all unit tests, which in large orgs can be practically impossible given the time constraints. How could they possibly know every single spot where that service is referenced? On top of that, imagine this service is referenced in your code a thousand times. Ten thousand times. How much of an effort would it take to change all of those unit tests when nothing that they are actually testing changes?

How would ApexMocks fix this?

Let’s go back to our original implementation of MyService.

[java]
public class MyService implements MyInterface{
public Integer calculateValues(Integer valueOne, Integer valueTwo){
return valueOne + valueTwo;
}
}
[/java]

However, let’s change our unit test to look like this:

[java]
@isTest
public class MyControllerTest{
public static testMethod void testCalculateValues(){
fflib_ApexMocks mocks = new fflib_ApexMocks();
MyInterface mockMyService = new MockMyService(mocks);

mocks.startStubbing();
mocks.when(mockMyService.calculateValues(5, 3)).thenReturn(8);
mocks.stopStubbing();

Test.startTest();
MyController controller = new MyController(mockMyService);
controller.valueOne = 5;
controller.valueTwo = 3;
Integer totalValue = controller.calculatedTotalValue();
Test.stopTest();

System.assertEquals(8, totalValue, ‘The service should return 8’);
}

private class MockMyService implements MyInterface{
private fflib_ApexMocks mocks;

public MockMyService(fflib_ApexMocks mocks){
this.mocks = mocks;
}

public Integer calculateValues(Integer valueOne, Integer valueTwo){
if (mocks.Stubbing){
mocks.prepareMethodReturnValue(this, ‘calculateValues’, new List {valueOne, valueTwo});
return null;
}else{
mocks.recordMethod(this, ‘calculateValues’, new List {valueOne, valueTwo});
fflib_MethodReturnValue methodReturnValue = mocks.getMethodReturnValue(this, ‘calculateValues’, new List {valueOne, valueTwo});

if (methodReturnValue != null){
if (methodReturnValue.ReturnValue instanceof Exception)
{
throw ((Exception) methodReturnValue.ReturnValue);
}

return (Integer) methodReturnValue.ReturnValue;
}
}

return null;
}
}
}
[/java]

Now I understand this looks much more complex, but the general idea behind this is that lines 28 to 48 can all be generated through an Ant script. How all of this works under the covers will be explained in a future article, for now, trust me when I say this unit test will pass. Essentially, the power is in lines 7 to 9. This is where the unit test is indicating that when the calculateValues method is called with the parameters of 5 and 3, it should return 8. It completely ignores actually running the method and just returns the value. This allows us to change our service without breaking our unit tests. Say we change the service back to:

[java]
public class MyService implements MyInterface{
public Integer calculateValues(Integer valueOne, Integer valueTwo){
return valueOne – valueTwo;
}
}
[/java]

When we run our unit tests, they still pass! The reason for this is due to the fact that the calculation is actually never run. I could change the service to:

[java]
public class MyService implements MyInterface{
public Integer calculateValues(Integer valueOne, Integer valueTwo){
Integer total = valueOne + valueTwo;
return total + valueOne + valueTwo;
}
}
[/java]

Still, our MyControllerTest unit test passes. This has finally allowed us to write a piece of code, and truly test just that piece of code.

Only The Beginning

The beauty of this is that it is just the tip of the iceberg of what is possible once true mocking is introduced. Over the next few weeks, I will be discussing all of the benefits and providing some information on how you can begin utilizing ApexMocks. It is open source so start using and contributing to the project today! Enjoy!

All code form this article is available as a Gist on GitHub.

Advertisement

Go to Smartblog Theme Options -> Ad Management to enter your ad code (300x250)

Comments are closed.