[SalesForce] mocking database methods using fflib

As fas as I know it is not possible to mock the methods under Database because salesforce does not allow mocking static methods. However, One can circumvent this limitation by wrapping the static method with another class. I would like to mock the method global static String executeBatch(Database.Batchable batchable, Integer batchSize). I am mocking using the fflib library, I wrote the following code but it trows an error when mocking my DatabaseWrapper class:

@IsTest
private class SuperTest {
    @IsTest
    static void method1Test() {
        fflib_ApexMocks mocks = new fflib_ApexMocks();

        IDatabaseWrapper databaseWrapperMock = (DatabaseWrapper) mocks.mock(DatabaseWrapper.class);

        Application.service.setMock(IDatabaseWrapper.class, databaseWrapperMock);
    }
}

public with sharing class DatabaseWrapper implements IDatabaseWrapper {
    public static IDatabaseWrapper newInstance() {
        return (IDatabaseWrapper) Application.service.newInstance(IDatabaseWrapper.class);
    }

    public String executeBatch(Database.Batchable<SObject> batchable, Integer batchSize) {
        return Database.executeBatch(batchable, batchSize);
    }
}

public interface IDatabaseWrapper {
    String executeBatch(Database.Batchable<SObject> batchable, Integer batchSize);
}

public class Application {
    public static final fflib_Application.ServiceFactory service = new fflib_Application.ServiceFactory(
            new Map<Type, Type>{
                    IDatabaseWrapper.class => DatabaseWrapper.class
            }
    );
}

The error message is:

System.TypeException: Class DatabaseWrapper__sfdc_ApexStub must implement the method: String IDatabaseWrapper.executeBatch(Database.Batchable<SObject>, Integer)
Class.System.Test.createStub: line 93, column 1
Class.fflib_ApexMocks.mock: line 67, column 1
Class.SuperTest.method1Test: line 7, column 1

Any idea what I am I doing wrong?

Best Answer

So, the pattern I use is taken from the Salesforce Lightning Platform Enterprise Architecture book Third Edition by Andrew Fawcett, Chapter 5 - Service Layer and Chapter 12, p 505 Unit testing a Controller method (service is being mocked) and the example code in the ApexMocks GitHub testmethods and Readme.md.

Interface

public interface IDatabaseWrapper {
    String executeBatch(String batchableName);
}

Service layer (redirector)

public class DatabaseWrapper {
 /**  
 * Factory creation of service object
 **/
 public static IDatabaseWrapper service() {
    return (IDatabaseWrapper) Application.Service.newInstance(IDatabaseWrapper.class);
 }
 public static String executeBatch(String batchableName) {
    service().executeBatch(batchableName);
 }
}

Service layer implementation

public class DatabaseWrapperImpl implements IDatabaseWrapper {
  public String executeBatch(String batchableName) {
     
     Integer batchSize = 200;  // you could also pass this in
     Type t = Type.forName(batchableClassName);
     Database.Batchable<Sobject> batchable = (Database.Batchable<Sobject>)t.newInstance();
     Database.executeBatch(batchable,batchSize); 
  }
}

Application.cls

public static final fflib_Application.ServiceFactory Service =
    new fflib_Application.ServiceFactory( 
        new Map<Type, Type> {
                IDatabaseWrapper.class  => DatbaseWrapperImpl.class
        });

Some code under test that uses DatabaseWrapper method

public class MyClass {
  public void myMethod(String batchableClassName) {
     
     String batchId = DatabaseWrapper.executeBatch(batchableClassName);
     // other work
  }
}

Test method

fflib_ApexMocks mocks = new fflib_ApexMocks();
// Given mock Service class
DatabaseWrapperImpl mockDatabaseWrapper = (DatabaseWrapperImpl) mocks.mock(DatabaseWrapperImpl.class);
// Given mock Service injected
Application.Service.setMock(IDatabaseWrapper.class,mockDatabaseWrapper); 

mocks.startStubbing();
mocks.when(mockDatabaseWrapper.executeBatch(fflib_Match.anyString())
            .thenReturn('someBatchId');
mocks.stopStubbing();

// when code under test is executed
new MyClass().myMethod('MyMockBatchable');
// Then verify 

When the MyClass.myMethod is invoked at run time, its implementation of executeBatch is mocked and the batchId is returned using the mocks.when - thenReturn construction within the stubbing.

I'd recommend you rename DatabaseWrapper to the fflib pattern - something like BatchServices.cls and BatchServicesImpl.cls. That is, everything referenced in Application should be a Domain, Selector, Service, or UnitOfWork

Related Topic