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()); } }