As of December 2017, a new matcher method has been added to ApexMocks: sObjectsWith(List<Map<Schema.SobjectField,Object>> expectedSobjects)
so, the testmethod now looks like this (no argument captors required):
fflib_ApexMocks mocks = new fflib_ApexMocks();
// Given mock implementation of UnitOfWork
fflib_SobjectUnitOfWork mockUow = (fflib_SobjectUnitOfWork) mocks.mock(fflib_SObjectUnitOfWork.class);
Application.UnitOfWork.setMock(mockUow);
// When method invoked
new Foo().doWork();
// Then verify new SObjects were created ...
((fflib_SObjectUnitOfWork)mocks.verify(mockUow,mocks.times(1)
.description('two accounts sb created')))
.registerNew(fflib_Match.sObjectsWith(
new List<Map<SObjectField,Object>> {
new Map<SObjectField,Object> {
Account.Name => 'A0',
Account.Website => 'www.salesforce.com'
},
new Map<SObjectField,Object> {
Account.Name => 'A1',
Account.Website => 'www.google.com'
}
}
));
The list of maps is compared in the same order as the sobjects presented to registerNew(someList)
. That is, the verify will fail if you coded this in the testmethod:
((fflib_SObjectUnitOfWork)mocks.verify(mockUow,mocks.times(1)
.description('two accounts sb created')))
.registerNew(fflib_Match.sObjectsWith(
new List<Map<SObjectField,Object>> {
new Map<SObjectField,Object> {
Account.Name => 'A1',
Account.Website => 'www.google.com'
},
new Map<SObjectField,Object> {
Account.Name => 'A0',
Account.Website => 'www.salesforce.com'
}
}
));
because the sobjects were presented to registerNew in the code under test as A0 followed by A1.
This one is a common mistake; the fflib Selector layer requires that you stub the SObjectType()
method as the setMock() method used by Application.Selector.setMock(mockSelector)
calls SObjectType()
to properly instantiate an internal map of selector instances by SObjectTypes (so some selectors can be mocked and others not).
I annotated your code below with the new line...
Now, some other things ...
You are testing getAccounts
so you need to verify that it returns the accounts returned by the (mocked) selector. So, your verify is not what you want
You have:
((AccountsSelector) MOCKS.verify(mockSelector)).newInstance().selectById(aid);
You need to verify the return from this method ...I altered your code below. There's no real need to verify that the selector was called because if the code under test doesn't pass in a set of mocked accountIds exactly the same as in your mocks.when, the selector will not return any Accounts and the code under test will not return any Accounts (will return NULL per the way you coded it)
@IsTest
private static void getAccounts() {
/* Create mocks */
fflib_ApexMocks MOCKS = new fflib_ApexMocks();
AccountsSelector mockSelector = (AccountsSelector) MOCKS.mock(AccountsSelector.class);
/* Generate a generic account id */
Id aid = fflib_IDGenerator.generate(Account.SObjectType);
//! Given
MOCKS.startStubbing();
List<Account> accountsList = new List<Account> {
new Account(
Id = aid,
Name = 'Test Account'
)
};
MOCKS.when(mockSelector.SObjectType()).thenReturn(Account.SObjectType); // REQUIRED
MOCKS.when(mockSelector.selectById(new Set<Id>{ aid }))).thenReturn(accountsList);
MOCKS.stopStubbing();
/* Configure the application to use mocks */
Application.Selector.setMock(mockSelector);
//! When
Account[] results = AccountsService.getAccounts(new Set<Id>{ aid })); // CHG
//! Then
/* Verify result was as expected */
System.assertEquals(1,results.size(),'sb return of mocked accounts');
}
Best Answer
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:
I also assume you have set up your Application class per guidelines.
Application.UnitOfWork.setMock(mockUow);
Application.Service.setMock(ISomeService.class,mockService);
Application.Selector.setMock(mockSelector);
Application.Domain.setMock(mockDomain);
Example: Assume an object under test (MyClass) calls an Accounts Selector
The mockSelector is expecting to be passed the contents of
someIdSet
(say id0 and id1) but if theMyClass
object actually callsselectById
with a different ID set, the stubbed selector will not return anything toMyClass
's object and results could go haywire.Using the same example as above
Your selector class's super class (
fflib_SObjectSelector
) requires your selector class to implement methodSObjectType()
so your mock selector must stub it.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 mockedselectByPostalCode
orselectById
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 beContactsSelector.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.Example
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 astartStubbing-stopStubbing
pairExample: You are mocking a service that receives as input a custom Apex Type
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
theequals()
andhashcode()
method - see Apex DocUse 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:
If you have code that looks like this:
and your
AccountsService.cls
has a redirector to the service factory methods like this:and your concrete service implementation class
AccountServiceImpl
looks like this:You have made a clumsy copy/paste error when you were adding the method in the
AccountsService.cls
Instead of
Use:
This is better covered in this Andrew Fawcett blog post but the upshot is you should verify unit of work
registerNew
orregisterUpdate
actions using the MatchersObjectWith
(again, see the blog post for how to do this).Let's say you are stubbing a service method
String result = doStuff(Integer i)
as follows:Now, if your runtime code under test calls
doStuff(..)
with a null argument, themocks.when
fflib_Match.anyInteger()
will return false - that is, the criteria for themocks.when(..)
will not be satisfied and no stub return valueFoo
is supplied back to the code under test. Instead,null
is returned and most likely, your testmethod will die on a null pointer exception.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:
and you verify that
doStuff(..)
was called with a collection that looked likesomeCollection
(say, a list of specific Ids), the verify fails because ApexMocks thinks, by the time youverify
, 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.verifyHere, I'm assuming your verify looks like:
Although there could be many reasons for this, check the following:
myMockedUowOrServiceOrSelectorOrDomain
is nullUPDATE 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
mocks.factory(..)
are out of date and should bemocks.mock(..)