I'm new in Apex Mocks but I'm really excited about all it's advantages. I'm trying to build test without DML. Here is a method I'm trying to test at the moment:
public Id createMmDocument(String senderId, String receiverId, Map<Id, Decimal> assetQtyByIds) {
final AST_AccountSelector.AST_IAccountSelector accountSelector = AST_AccountSelector.newInstance();
final Map<Id, Account> accountsMap = new Map<Id, Account>(accountSelector.selectById(new Set<Id>{senderId, receiverId}));
Account sender = accountsMap.get(senderId);
Account receiver = accountsMap.get(receiverId);
validateMmCreatorDocumentData(sender, receiver);
final fflib_ISObjectUnitOfWork uow = (fflib_SObjectUnitOfWork)AST_Application.unitOfWork.newInstance(
new List<SObjectType> {
AST_Material_Document__c.SObjectType,
AST_Material_Document_Item__c.SObjectType,
AST_SerialNumber__c.SObjectType
}
);
AST_Material_Document__c document = new AST_Material_Document__c(
RecordTypeId = MM_MATERIAL_DOCUMENT_RECORD_TYPE_ID,
AST_Document_Date__c = Date.today(),
AST_Receiving_Contractor__c = receiverId,
AST_InternalSender__c = senderId
);
uow.registerNew(document);
final AST_AssetSelector.AST_IAssetSelector assetSelector = AST_AssetSelector.newInstance();
final Map<Id, Asset> assets = new Map<Id, Asset>(assetSelector.selectWithQtyAvailableAndMaterialAssetIdById(assetQtyByIds.keySet()));
List<AST_Material_Document_Item__c> documentItems = insertMmDocumentItems(document.Id, assets, assetQtyByIds, sender, receiver, uow);
insertSerialNumbers(documentItems, assets, uow);
uow.commitWork();
return document.Id;
}
I have couple of questions regarding the right approach when coding the unit test:
- There are three class private methods fired in this method. As they are private I'm not able to put them in class interface so I'm not able to mock them and verify - what is the right approach? In an example
validateMmCreatorDocumentData(sender, receiver)
checks permissions and throws when lack of permission and I would like to avoid this throwing when unit testing logic above. How this should be done? - Two of this private methods fired contain Unit Of Work as parameter. Look at example of
insertSerialNumbers(documentItems, assets, uow)
Is this right approach and how to unit test this private methods while I can't mock uow as it is not received from Application class in this private methods? - Test below fails with following exception:
Class.fflib_SObjectUnitOfWork.registerNew: line 271, column 1
Class.fflib_SObjectUnitOfWork.registerNew: line 245, column 1
Class.AST_MaterialDocumentServiceTest.createMmDocumentTest: line 698, column 1
10:27:09.4 (818440711)|FATAL_ERROR|System.NullPointerException: Attempt to de-reference a null object
and I'm not sure what is wrong as Match always returns null...?
4. And final question is: how to avoid result of uow.commitWork()
? Firing test causes DML exception as it tries to insert records to database. I managed to avoid this behavior adding custom fflib_SObjectUnitOfWork.IDML
like you can see below but I'm not sure it is right way?
@IsTest
private static void createMmDocumentTest() {
final Id senderId = fflib_IDGenerator.generate(Account.SObjectType);
final Id receiverId = fflib_IDGenerator.generate(Account.SObjectType);
final Id assetId = fflib_IDGenerator.generate(Account.SObjectType);
final Map<Id, Decimal> assetQuantities = new Map<Id, Decimal>{
assetId => 1.00
};
final Id shopAccountRecordTypeId = Schema.SObjectType.Account.recordTypeInfosByDeveloperName.get(FRM_Constants.MPK_RECORD_TYPE_DEV).getRecordTypeId();
Account sender = new Account(Id = senderId, RecordTypeId = shopAccountRecordTypeId);
sender = (Account) fflib_ApexMocksUtils.setReadOnlyFields(
sender,
Account.class,
new Map<SObjectField, Object>{
Account.AST_MPK__c => 'Z0099'
}
);
Account receiver = new Account(Id = receiverId, RecordTypeId = shopAccountRecordTypeId);
receiver = (Account) fflib_ApexMocksUtils.setReadOnlyFields(
receiver,
Account.class,
new Map<SObjectField, Object>{
Account.AST_MPK__c => 'Z0011'
}
);
final fflib_ApexMocks mocks = new fflib_ApexMocks();
final AST_AccountSelector accountSelectorMock = (AST_AccountSelector) mocks.mock(AST_AccountSelector.class);
final fflib_ISObjectUnitOfWork uowMock = new fflib_SObjectUnitOfWork(new List<SObjectType>{
AST_Material_Document__c.SObjectType,
AST_Material_Document_Item__c.SObjectType,
AST_SerialNumber__c.SObjectType
}, new FakeDmlForMockingUnitOfWork());
mocks.startStubbing();
mocks.when(accountSelectorMock.sObjectType()).thenReturn(Account.SObjectType);
mocks.when(accountSelectorMock.selectById(new Set<Id>{
senderId, receiverId
})).thenReturn(new List<Account>{
sender, receiver
});
mocks.stopStubbing();
AST_Application.selector.setMock(accountSelectorMock);
AST_Application.unitOfWork.setMock(uowMock);
AST_MaterialDocumentService.AST_IMaterialDocumentService service = AST_MaterialDocumentService.newInstance();
Exception exceptionThrown;
try {
service.createMmDocument(senderId, receiverId, assetQuantities);
} catch (Exception ex) {
exceptionThrown = ex;
}
fflib_ApexMocks.ApexMocksException mockException;
try {
final Id mmMaterialDocumentRecordType = Schema.SObjectType.AST_Material_Document__c.recordTypeInfosByDeveloperName.get(AST_MaterialDocumentService.MM_RECORD_TYPE_DEV).getRecordTypeId();
//line 698 - below verification fails:
((fflib_ISObjectUnitOfWork) mocks.verify(uowMock, mocks.times(1).description('New material document was not registered'))).registerNew(fflib_Match.sObjectWith(
new Map<SObjectField, Object> {
AST_Material_Document__c.RecordTypeId => mmMaterialDocumentRecordType,
AST_Material_Document__c.AST_Document_Date__c => Date.today(),
AST_Material_Document__c.AST_Receiving_Contractor__c => receiverId,
AST_Material_Document__c.AST_InternalSender__c => senderId
}
));
((fflib_ISObjectUnitOfWork) mocks.verify(uowMock, mocks.times(1).description('Unit of work not committed'))).commitWork();
} catch (fflib_ApexMocks.ApexMocksException ex) {
mockException = ex;
}
System.assertEquals(null, mockException, MOCK_VERIFY_FAILED);
}
public class FakeDmlForMockingUnitOfWork implements fflib_SObjectUnitOfWork.IDML {
public void dmlDelete(List<SObject> objList) {
}
public void dmlInsert(List<SObject> objList) {
}
public void dmlUpdate(List<SObject> objList) {
}
public void emptyRecycleBin(List<SObject> objList) {
}
public void eventPublish(List<SObject> objList) {
}
}
}