[SalesForce] How to unit test service layer without DML

Looking for best practices/suggestions on how to unit test a service layer method that creates new records based on input without having to issue the DML.

An example would be OpportunitiesServiceImpl.createInvoices in fflib apex sample code. Given a list of opportunities, new Invoice__c & InvoiceLine__c records are created. The method could be tested by mocking UnitOfWork, but how to verify that the data on Invoice__c & InvoiceLine__c is correct. In other words, how to determine that invoice__c.InvoiceDate__c was set to opportunityRecord.CloseDate instead of opportunityRecord.SomeOtherDate?

The challenge with DML is that in more complicated situations then the one presented in createInvoices, related data might to exist in the DB which means it all needs to be setup, inserted, etc. Also, domain logic (onBefore/onAfter/etc) will fire when records are inserted and complicates situations and takes away from a true unit test.

Some ideas:

  1. Stuck with DML – bad
  2. Create a InMemoryDB and build a UOW that will add to InMemoryDB instead of calling insert/update/etc. – Possibly an enhancement to fflib common?
  3. Have createInvoices return a wrapper class and inspect the wrapper class (e.g. InvoiceFactory concept with Invoices property). The challenge here is that you are validating the wrapper class not the Entity (SObject) and following this approach results in lots of wrapper classes when you have lots of service methods that do similar things in different areas of your app

Appreciate any thoughts/input, thank you!

Update – With some great insight from @andyinthecloud, the registerNew/registerDirty/etc. methods on the UOW itself can be verified to check the data resulted in the way expected. This avoids having to issue the DML. One note is to be aware of https://github.com/financialforcedev/fflib-apex-mocks/issues/8 which is what led me down the path of posting this since my attempts at this approach were not working as I expected. In summary, here's the approach:

// Create Mocks
fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_ISObjectUnitOfWork uowMock = new fflib_SObjectMocks.SObjectUnitOfWork(mocks);

// Set Mock
Application.UnitOfWork.setMock(uowMock);

// Call Service
MyService.doStuffThatWillInsertAccountRecord();

// create an account that should be the same as what
// registerNew was passed
Account expectedResult = new Account(Name = 'foo' .... );
((fflib_ISObjectUnitOfWork)
mocks.verify(uowMock, 1)).registerNew(expectedResult);
((fflib_ISObjectUnitOfWork)
mocks.verify(uowMock, 1)).commitWork();

Best Answer

There is a 'smoking hot' answer provided by @sfdcfox that exploits Json.deserialize to build in-memory relationships.

You can place your JSON strings in a library of static resources to mimic many use cases and cycle through those static resources in your test class/testmethods

Related Topic