Using ApexMocks is a powerful way to improve the unit testing of your code base and significantly improve the total turnaround time for all your tests as many tests can be done without having to do any DML
ApexMocks is a framework that works with well-designed applications that support dependency injection. However, you have to use it as designed otherwise your mocking results may not work as expected.
I'm going to make the assumption that you are using Apex Mocks in conjunction with the Force.com Enterprise Architecture pattern (aka fflib) as seen on Trailhead here and here
Some common reasons why your mocks aren't working
Note I assume you have at the beginning of your test method:
fflib_ApexMocks mocks = new fflib_ApexMocks();
I also assume you have set up your Application class per guidelines.
You forgot to inject the mock object (service, selector, domain,
or unit of work)
- The injection should be done after any mock object stubbing
- For Unit Of Work:
Application.UnitOfWork.setMock(mockUow);
- For Service:
Application.Service.setMock(ISomeService.class,mockService);
- For Selector:
Application.Selector.setMock(mockSelector);
- For Domain:
Application.Domain.setMock(mockDomain);
You are trying to stub a dependency but the code under test is
passing arguments that are different than your mocked dependency
expects
Example: Assume an object under test (MyClass) calls an Accounts Selector
AccountsSelector mockAccountsSelector = (AccountsSelector) mocks.mock(AccountsSelector.class);
mocks.startStubbing();
mocks.when(mockAccountsSelector.SObjectType()).thenReturn(Account.SObjectType);
mocks.when(mockAccountsSelector.selectById(someIdSet)).thenReturn(someMockAccounts);
mocks.stopStubbing();
Application.Selector.setMock(mockAccountsSelector);
new MyClass().doStuff(); // code under test
The mockSelector is expecting to be passed the contents of someIdSet
(say id0 and id1) but if the MyClass
object actually calls selectById
with a different ID set, the stubbed selector will not return anything to MyClass
's object and results could go haywire.
You are trying to stub a Selector dependency but forgot to stub
the SObjectType() method
Using the same example as above
AccountsSelector mockAccountsSelector = (AccountsSelector) mocks.mock(AccountsSelector.class);
mocks.startStubbing();
// stub the SObject() method AND the specific selector method
mocks.when(mockAccountsSelector.SObjectType())
.thenReturn(Account.SObjectType);
mocks.when(mockAccountsSelector.selectById(someIdSet))
.thenReturn(someMockAccounts);
mocks.stopStubbing();
Application.Selector.setMock(mockAccountsSelector);
Your selector class's super class (fflib_SObjectSelector
) requires your selector class to implement method SObjectType()
so your mock selector must stub it.
You are stubbing the wrong methods
Example:
You have a Contacts selector with methods selectById, selectByEmail, selectByPostalCode(). Your code under test calls selectByEmail
but you didn't mock that method (or mocked selectByPostalCode
or selectById
by mistake).
This error comes up often when code is modified after unit testing is done. Imagine once you had some useful code that did ContactsSelector.newInstance().selectById(..)
and you mocked that method in your unit test. Six months later, you go into the code and change the selector to be ContactsSelector.newInstance.selectByEmail(..)
and then rerun your unit tests. They will fail because the wrong method is mocked in the unit test and hence no Contacts will be returned to the code under test.
You forgot to close your stubbing with `stopStubbing`
Example
AccountsSelector mockAccountsSelector = (AccountsSelector) mocks.mock(AccountsSelector.class);
mocks.startStubbing();
// stub the SObject() method AND the specific selector method
mocks.when(mockAccountsSelector.SObjectType())
.thenReturn(Account.SObjectType);
mocks.when(mockAccountsSelector.selectById(someIdSet))
.thenReturn(someMockAccounts);
mocks.startStubbing(); // OOPS! should be stopStubbing()
Application.Selector.setMock(mockAccountsSelector);
The failure to close the stubbing can lead to unpredictable results (and this is a common issue if you are copy-pasting the mocks.startStubbing()
line!)
Note that mocks.doThrowWhen(..)
must be enclosed within a startStubbing-stopStubbing
pair
You get a null point exception on your mocks.when(...).thenReturn(xxx) line
Example: You are mocking a service that receives as input a string
AccountsServiceImpl mockAccountsService = (AccountsServiceImpl)
mocks.mock(AccountsServiceImpl.class);
mocks.when(mockAccountsService.execute('string00')
.thenReturn(somethingUseful);
Application.Service.setMock(mockAccountsService);
new MyClass().doStuff(); // code under test
Here, you have forgotten to include the mocks.startStubbing()
-- mocks.stopStubbing()
scoping. `mocks.when().thenReturn() requires the stubbing scope to be established.
You are using Apex types as arguments to stubbed methods without a
way to test equality
Example: You are mocking a service that receives as input a custom Apex Type
AccountsServiceImpl mockAccountsService = (AccountsServiceImpl)
mocks.mock(AccountsServiceImpl.class);
mocks.startStubbing();
mocks.when(mockAccountsService.execute(myApexTypes)
.thenReturn(somethingUseful);
mocks.stopStubbing();
Application.Service.setMock(mockAccountsService);
new MyClass().doStuff(); // code under test
The issue here that by default, the stubbing framework has no way to test the equality of the collection myApexTypes
against the actual collection of ApexType that you pass in the code under test (i.e. doStuff()
). Note that Primitives/Sobjects can be tested for equality as can collections of primitives or sobjects.
To get around this, you either have to
Implement for class MyApexType
the equals()
and hashcode()
method - see Apex Doc
Use a matcher that ignores the contents of the specific collection (defeating somewhat the rigor of your unit test for some use cases). Do this with a stubbing line such as:
mocks.when(mockAccountService.execute((Set<MyApexType> fflib_Match.anyObject())
.thenReturn(somethingUseful);
Your service redirector redirects to the wrong method
If you have code that looks like this:
AccountsService.doSomething(args);
and your AccountsService.cls
has a redirector to the service factory methods like this:
public static void doSomething(args) {
service().doSomethingElse(args);
}
public static void doSomethingElse(args) {
service().doSomethingElse(args);
}
and your concrete service implementation class AccountServiceImpl
looks like this:
public class AccountsServiceImpl implements IAccountsService {
public void doSomething(Object args) {..}
public void doSomethingElse(Object args) {..}
}
You have made a clumsy copy/paste error when you were adding the method in the AccountsService.cls
Instead of
public static void doSomething(args) {
service().doSomethingElse(args); // WRONG redirection
}
Use:
public static void doSomething(args) {
service().doSomething(args); // RIGHT redirection
}
You are trying to verify that Unit Of Work was passed an expected
SObject but you are not verifying properly
This is better covered in this Andrew Fawcett blog post but the upshot is you should verify unit of work registerNew
or registerUpdate
actions using the Matcher sObjectWith
(again, see the blog post for how to do this).
You are trying to use a Matcher but the runtime arg is null
Let's say you are stubbing a service method String result = doStuff(Integer i)
as follows:
AccountsServiceImpl mockAccountsService =
(AccountsServiceImpl) mocks.mock(AccountsServiceImpl.class);
mocks.startStubbing();
mocks.when(mockAccountsService.doStuff(fflib_Match.anyInteger()))
.thenReturn('Foo');
mocks.stopStubbing();
// Inject the mockService
Application.Service.setMock(mockAccountsService);
Now, if your runtime code under test calls doStuff(..)
with a null argument, the mocks.when
fflib_Match.anyInteger()
will return false - that is, the criteria for the mocks.when(..)
will not be satisfied and no stub return value Foo
is supplied back to the code under test. Instead, null
is returned and most likely, your testmethod will die on a null pointer exception.
Your code under test, after calling the mocked object, modified the argument used in the call to a mocked object
ApexMocks captures all method calls and their arguments. If the arguments are non-primitives, these are captured by reference. Thus, when you verify
, you are most likely verifying the arguments as were passed to the mocked object but if the code under test subsequently modified those arguments, the verify will fail because you have also implicitly modified the recorded argument!.
That is, ApexMocks, when it records the method call + arguments doesn't make a copy of those arguments (if non-primitive).
For example, if your code under test does this:
someMockableService.doStuff(someCollection);
someCollection.clear();
and you verify that doStuff(..)
was called with a collection that looked like someCollection
(say, a list of specific Ids), the verify fails because ApexMocks thinks, by the time you verify
, the recorded argument (originally non-empty) is now an empty list!
You can read a bigger example of this in ApexMocks GitHub Issue #78
You get a System.NullPointerException when doing a mocks.verify
Here, I'm assuming your verify looks like:
((MyType)mocks.verify(myMockedUowOrServiceOrSelectorOrDomain,mocks.times(1)
.description('some helpful message')))
.myMethod(expectedArg0, expectedArg1, ...);
Although there could be many reasons for this, check the following:
myMockedUowOrServiceOrSelectorOrDomain
is null
UPDATE 2021-03-01
Additional diagnostic help is provided with the ApexMocks library as of Pull Request 112 which includes Feature/Better Verification Messages. This will show the actual args versus expected args rather than prior versions which only told you that the matcher failed, displaying only counts.
Additional References