Part five of a series. Posts include:
- Apex Mocks and Enterprise Patterns
- Apex Mocks and Email
- Apex Mocks, Selectors and Formula Fields
- Apex Mocks and no-arg void Domain methods
- Apex Mocks and Verifying Multiple Custom Type Arguments
Let’s say you have an AccountsService with method doStuff that has two arguments: an fflib_SObjectUnitOfWork and a custom type Map. CancelRequest looks like this:
public class CancelRequest {
Id id;
Date cancelDate;
String cancelReason;
public CancelRequest(Id val) {this.id = val;}
public CancelRequest withCancelDate(Date val) {this.cancelDate = val; return this;}
public CancelRequest withCancelReason(String val) {this.cancelReason = val; return this;}
public Date getCancelDate() {return this.cancelDate;}
public String getCancelReason() {return this.cancelReason;}
}
Now, let’s say you have some code that calls the AccountsService.cancel (this could be a Visualforce controller, invocable method, domain class method, batchable execute(), Apex REST class, etc. – for purposes of this example, it doesn’t matter).
public MyClass {
public void doStuff(Set<Id> accountIds, String cancelReason) {
fflib_ISobjectUnitOfWork uow = Application.UnitOfWork.newInstance();
Map<Id,CancelRequest> cancelRequests = new Map<Id,CancelRequest>();
for (Id accountId: accountIds) {
cancelRequests.put(accountId,new CancelRequest(accountId)
.withCancelDate(Date.today())
.withCancelReason(cancelReason) );
}
AccountsService.cancel(uow,cancelRequests); // cancel accounts w/ date+reason
uow.commitWork();
}
}
To unit test MyClass.doStuff(..), you want to mock the AccountsService as all you’re really interested in is that it is called once and with the proper arguments. You have a separate unit test for the actual AccountsService.cancel that checks for the proper DML.
So, let’s build the testmethod…
@isTest
private static void givenAccountIdsAndReasonVerifyDelegationToAccountsServiceCancel() {
fflib_ApexMocks mocks = new fflib_ApexMocks(); // framework
// Given some accountIds
Id[] mockAccountIds = new List<Id> {
fflib_IdGenerator.generate(Account.SObjectType),
fflib_IdGenerator.generate(Account.SObjectType)
};
// Given a cancel reason
String cancelReason = 'foo';
// Given a mockAccountsService (assumes standard naming conventions for service implementations
AccountsServiceImpl mockAccountsService =
(AccountsServiceImpl) mocks.mock(AccountsServiceImpl.class);
Application.Service.setMock(IAccountsService.class,mockAccountsService);
// Given a mock Unit of Work
fflib_SObjectUnitOfWork mockUow =
(fflib_SObjectUnitOfWork) mocks.mock(fflib_SObjectUnitOfWork.class);
Application.UnitOfWork.setMock(mockUow);
// When doStuff called
Test.startTest();
new MyClass().doStuff(new Set<Id> (mockAccountIds),cancelReason);
Test.stoptest();
// Then verify service was called only once
((AccountsServiceImpl) mocks.verify(mockAccountsService,mocks.times(1)
.description('AccountsService.cancel sb called')))
.cancel((fflib_ISobjectUnitOfWork) fflib_Match.anyObject(),
(Map<Id,CancelRequest>) fflib_Match.anyObject()
);
// Then verify that the service was called with the expected arguments.
// Because the arguments are Apex custom types (and don't implement
// an equals() and hashcode() method), there is no way for the ApexMocks
// matchers to verify on equality. So, we fallback to argumentcaptors
// Set up the captors, one per arg to method AssetsService.cancel
fflib_ArgumentCaptor capturedUowArg = // arg0
fflib_ArgumentCaptor.forClass(fflib_ISObjectUnitofWork.class);
fflib_ArgumentCaptor capturedCancelRequestArg = // arg1
fflib_ArgumentCaptor.forClass(Map<Id,CancelRequest>.class);
// Capture the actual args used when the mock service was called
((AccountsServiceImpl) mocks.verify(mockAccountsService,1))
.cancel((fflib_ISobjectUnitOfWork)capturedUowArg.capture(),
(Map<Id,CancelRequest>)capturedCancelRequestArg.capture()
);
// Transform the capturedArgs (represented by type fflib_ArgumentCaptor)
// into something we can inspect (using getValue() )
fflib_ISobjectUnitOfWork actualUowArg =
(fflib_ISobjectUnitOfWork) capturedUowArg.getValue();
Map<Id,CancelRequest> actualCancelRequestArg =
(Map<Id,CancelRequest>) capturedCancelRequestArg.getValue();
// Now, whew, finally verify values
System.assertEquals(mockAccountIds.size(),
actualCancelRequestArg.size(),'all accts sb requested for cancel');
for (Id accountId: actualCancelRequestArg.keySet() ) {
System.assertEquals('Foo',
actualCancelRequestArg.get(accountId).getCancelReason());
System.assertEquals(Date.today(),
actualCancelRequestArg.get(accountId).getCancelDate());
}
}
