for fflib / apexmocks users
Let's say you have a class/method that inserts new objects where a field value is non-deterministic, say, a UUID
public class AccountsServiceImpl {
public void makeAccount() {
fflib_ISobjectUnitOfWork uow = Application.UnitOfWork.newInstance();
uow.registerNew(new Account (Name = UUID.getV4());
uow.commitWork();
}
}
The method UUID.getV4()
constructs a V4-compliant UUID using a Cryptographic random number
The normal testmethod ApexMocks pattern for verifying an sobject would be:
fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_SObjectUnitOfWork mockUow = (fflib_SObjectUnitOfWork) mocks.mock(fflib_SObjectUnitOfWork.class); // the mock UnitOfWork
Application.UnitOfWork.setMock(mockUow); // injected for the factory to see
// When code-under-test executed
AccountsServiceImpl.makeAccount();
// Then verify Account fields constructed as expected
((fflib_SObjectUnitOfWork)mocks.verify(mockUow,mocks.times(1).description('account should be constructed as expected')))
.registerNew(fflib_Match.sObjectWith(new Map<SObjectField,Object> {
Account.Name => ?? // what goes here ??
}));
But, you have no constant to assign to the Matcher sObjectWith
for field Account.Name
as the value is determined randomly.
Assuming you wish to avoid DML in this unit test method, what do you do?
Best Answer
There are two approaches to solve this
Dependency-injection of mock UUIDs into the UUID class
There would be all sorts of ways to do this including:
Your testmethod now looks like:
Use ApexMocks ArgumentCaptor
ArgumentCaptors allow the object passed to
uow.registerNew(theSObject)
to be inspected during the testmethod execution. This works because since we are mocking the UnitOfWork object, the ApexMocks (i.e.Test.StubAPI
) framework is capturing all of the arguments passed to every method in the mocked object (theuow
). As the framework has captured the arguments, they can be inspected later.The syntax is a bit wonky but it goes like this:
Notes
1 - It is left to the reader to add assertions to verify that each constructed Account in the transaction has a different UUID generated (assuming the code under test creates 2+ Accounts) 2 - On the line
fflib_ArgumentCaptor capturedArg = fflib_ArgumentCaptor.forClass(SObject.class);
you would think you could do this:and capture only the
registerNew
of Account SObjects, ignoring anyregisterNew
for Contact, Order, etc. But this sadly doesn't work as per this comment in thefflib_ArgumentCaptor
class