Testing service methods which fflib unit of work in method’s signature

apexapexmockfflibunit-test

So what I'm tying to do is to unit test one of methods in my service:

 @TestVisible
    private void insertSerialNumbers(List<AST_Material_Document_Item__c> documentItems, Map<Id, Asset> assetMap, fflib_SObjectUnitOfWork uow) {
        for (AST_Material_Document_Item__c item : documentItems) {
            if (item.AST_SerialNumberRequired__c) {
                AST_SerialNumber__c serial = new AST_SerialNumber__c(
                        AST_MaterialDocumentPosition__c = item.Id,
                        Name = assetMap.get(item.AST_RelatedAsset__c).SerialNumber,
                        AST_CreatorFlag__c = true
                );
                uow.registerNew(serial);
            }
        }
    }

with test method as below:

@IsTest
    private static void insertSerialNumbersTest() {
        Id itemId = fflib_IDGenerator.generate(AST_Material_Document_Item__c.SObjectType);
        Id assetId = fflib_IDGenerator.generate(Asset.SObjectType);
        AST_Material_Document_Item__c item = new AST_Material_Document_Item__c(
                Id = itemId,
                AST_SerialNumberRequired__c = true,
                AST_RelatedAsset__c = assetId
        );
        Asset relatedAsset = new Asset(Id = assetId, SerialNumber = 'fakeSerialNumber');
        Map<Id, Asset> assets = new Map<Id, Asset>{assetId => relatedAsset};
        List<AST_Material_Document_Item__c> documentItems = new List<AST_Material_Document_Item__c>{item};
        fflib_ApexMocks mocks = new fflib_ApexMocks();
        fflib_SObjectUnitOfWork mockUow = (fflib_SObjectUnitOfWork) mocks.mock(fflib_SObjectUnitOfWork.class);

        AST_MaterialDocumentService service = AST_MaterialDocumentService.newInstance();
        service.insertSerialNumbers(documentItems, assets, mockUow);

        ((fflib_SObjectUnitOfWork)mocks.verify(mockUow, mocks.times(1).description('Missed serial number insert')))
                .registerNew(fflib_Match.sObjectsWith(
                new List<Map<SObjectField, Object>> {
                        new Map<SObjectField, Object> {
                                AST_SerialNumber__c.AST_MaterialDocumentPosition__c => itemId,
                                AST_SerialNumber__c.Name => relatedAsset.SerialNumber,
                                AST_SerialNumber__c.AST_CreatorFlag__c => true
                        }
                }
        ));
    }

Unfortunately with no success:

fflib_ApexMocks.ApexMocksException: EXPECTED COUNT: 1
ACTUAL COUNT: 0
METHOD: fflib_SObjectUnitOfWork__sfdc_ApexStub.registerNew(List<SObject>)
Missed serial number insert
---
ACTUAL ARGS: ()
---
EXPECTED ARGS: [ordered SObjects with [{"AST_CreatorFlag__c":true,"Name":"fakeSerialNumber","AST_MaterialDocumentPosition__c":"a0s000000000001AAA"}]]

Does anyone of you had such issue with testing method which get unit of work as method input? Is this a reason that this mock verification fails?

Best Answer

Your first port of call should be this answer. See also this recent answer

Normally, one needs to inject the mock UnitOfWork into the fflib factories as such:

Application.UnitOfWork.setMock(mockUow);

But in your case, this isn't necessary as the code under test is passed the mockUow directly:

service.insertSerialNumbers(documentItems, assets, mockUow);

Your verify is trying to verify a list of sobjects using the matcher fflib_Match.sObjectsWith but your code under test calls registerNew in a loop.

Thus, you need this verify:

((fflib_SObjectUnitOfWork)mocks.verify(mockUow, mocks.times(1).description('Missed serial number insert')))
            .registerNew(fflib_Match.sObjectWith(  // singular
                    new Map<SObjectField, Object> {
                            AST_SerialNumber__c.AST_MaterialDocumentPosition__c => itemId,
                            AST_SerialNumber__c.Name => relatedAsset.SerialNumber,
                            AST_SerialNumber__c.AST_CreatorFlag__c => true
                    }
            
    ));

.. and again for each AST_SerialNumber__c your code under test creates

Summary

When verifying a call to the unitOfWork, you need to use the relevant matcher that corresponds to the code under test. If the call to the unitOfWork method has multiple arguments, you need to use matchers for each argument (for example, matching uow.registerRelationship)