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.