2

Would be great if someone could point me in the right direction here.

I have this static method as follows...

public static void createDupeMap(Lead[] leads) {
    
    CheckExistingRecords cer = new CheckExistingRecords();
    List<Datacloud.FindDuplicatesResult> results = cer.findDuplicates(leads);

    //parse results
    if(results!=null && results.size() > 0) {
        for (Integer i=0;i<results.size();i++) {
            for (Datacloud.DuplicateResult dr : results[i].getDuplicateResults()) {
               //do stuff with results
            }
        }
    }
}

CheckExistingRecords is a simple class that wraps the Datacloud.FindDuplicates.findDuplicates() method call.

I want to mock/stub the findDuplicates() method so that I have a bunch of expected results I can check for once this createDupeMap method has done it's stuff.

I also then want this mocked method to be pulled in when I do an integration test (so I'm not creating and passing in mocked objects etc).

So I can implement System.StubProvider to mock the CheckExistingRecords class. The bit I'm stuck on is how to instruct my unit test to employ this mocked class, when in my integration test I'm calling a method much further up the chain.

It sounds reasonable to me but I'm going round in circles a bit here.

Thanks

1 Answer 1

5

I'm going to give the answer here in terms of the fflib pattern and ApexMocks (which exploits the StubAPI). The key is to create a service layer that can return a factory-generated service implementation. The factory can return either a mocked service or the real (concrete) service. The code-under-test does not need to know which is present at any given time of execution.

Application.cls

public static final fflib_Application.ServiceFactory Service = 
        new fflib_Application.ServiceFactory( 
            new Map<Type, Type> {
                    IAccountsService.class              => AccountsServiceImpl.class, 
                     ...
                    ICheckExistingRecordsService.class  => CheckExistingRecordsServiceImpl.class
            });

Service Interface

public interface ICheckExistingRecordsService{   
    List<Datacloud.FindDuplicatesResult> findDuplicates(List<Lead> leads);
}

Service redirector/factory

public inherited sharing class CheckExistingRecordsService{
    
    public static List<Datacloud.FindDuplicatesResult> findDuplicates(List<Lead> leads) {
        service().findDuplicates(leads);
    }
        
    private static ICheckExistingRecordsService service() {     // factory  
        return (ICheckExistingRecordsService) Application.Service.newInstance(ICheckExistingRecordsService.class);
    }
}

Concrete implementation

public class CheckExistingRecordsServiceImpl implements ICheckExistingRecordsService{
    
    public virtual void findDuplicates(List<Lead> leads) {
        // do the work here
    }

You'll need to change the way you invoke the service in your code to:

List<Datacloud.FindDuplicatesResult> results = CheckExistingRecordsService.findDuplicates(leads);

For tests that need to mock this service

// Given some mockDataCloudDuplicates
List<Datacloud.FindDuplicatesResult> mockDups = new List<Datacloud.FindDuplicatesResult>{
     new Datacloud.FindDuplicatesResult ( ... init sobject ),
     new Datacloud.FindDuplicatesResult (..),
     ... };

// Given mock service that returns mock dups
CheckExistingRecordsServiceImpl mockCheckExistingRecordsService = (CheckExistingRecordsServiceImpl ) mocks.mock(CheckExistingRecordsServiceImpl .class);
mocks.startStubbing();
mocks.when(mockCheckExistingRecordsService.findDuplicates(
            (Lead[])fflib_Match.anyObject()  // Leads
            .thenReturn(mockDups);
mocks.stopStubbing
//  Given mock Service Injected
Application.Service.setMock(ICheckExistingRecordsService.class,mockCheckExistingRecordsService );
 
// when code-under-test invoked
... whatever

// then verify results 

So; basically, every request to the CheckExistingRecordsService.findDuplicates will use the concrete CheckExistingRecordsServiceImpl class unless a mock service is injected in the testmethod:

CheckExistingRecordsServiceImpl mockCheckExistingRecordsService = (CheckExistingRecordsServiceImpl ) mocks.mock(CheckExistingRecordsServiceImpl .class);

The injection happens through the magic of the Application class which serves as the factory for constructing an object of either the concrete or mocked service. The call to the factory is done in the service class on this line:

private static ICheckExistingRecordsService service() {     // factory  
        return (ICheckExistingRecordsService) Application.Service.newInstance(ICheckExistingRecordsService.class);
    }

Here's a link to the fflib_Application.cls that does this work

OK, a bit long-winded but I can't overemphasize the value of the Enterprise Patterns (aka fflib) to organizing your code to facilitate separation of concerns and mocked implementations of:

  • Selectors (so you can return mocked sobjects for any query and avoid DML'ing test data)
  • Services (as shown in the answer to your question)
  • Domains (especially useful in unit testing trigger handlers; mockable when you have domain methods other than trigger handlers)
  • Units of Work (so you can unit test code that does DML without actually doing the DML)

References

Notes

  • ApexMocks isn't the only mocking framework around - just happens to be the one I'm most familiar with.
8
  • Hey @cropredy that's some comprehensive answer - thank you. I'll get onto this tomorrow and get my head around it. Yes it does seem like quite a lot of work but perhaps I'll find myself rewriting a lot of my unit tests on the back of this.
    – edralph
    Commented Nov 26, 2020 at 17:46
  • 1
    chapter 12 of the book (unit testing) was revelatory for me when I first read it (and suggests some alternate, non-fflib mocking patterns). I had read lots of blog posts by Fawcett over the years but the book tied it all together into a coherent piece that was aligned with the latest fflib code on github. Apexmocks is based on Mockito, the mocking framework for Java. John Daniels has some DF sessions that take this a step further into 2GP. You can use apexmocks w/o fflib patterns but you're going to need a factory and a way to tell the factory to provide a mocked object
    – cropredy
    Commented Nov 26, 2020 at 18:04
  • 1
    Yes. That is correct.
    – cropredy
    Commented Nov 26, 2020 at 21:55
  • 1
    Use Json.deserialize technique. Or use the nice wrapper for this in the GitHub SobjectFabricator by Matt Addy
    – cropredy
    Commented Dec 3, 2020 at 17:50
  • 1
    You may need to create a proxy object for this. I had to do the same for AggregateResult which also can’t be constructed.
    – cropredy
    Commented Dec 4, 2020 at 15:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .