1

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?

1 Answer 1

2

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

3
  • Thank you, your response solved my issue. I think is a good idea to change the names according to the fflib pattern. Question, mainly salesforce does not allow to mock methods with the arguments "Database.Batchable<Sobject>", right? That is why I need to pass the class by name? Commented Aug 14, 2020 at 10:57
  • I just used name as an arg to make it easier from a config point of view. You could pass as an arg the batchable object. Same principle applies. The service Impl class is what wraps the Database.executeBatch call (no apex OOTB class can be mocked. You are correct on that score)
    – cropredy
    Commented Aug 14, 2020 at 14:44
  • It does work with the String argument, but not at all with the "Database.Batchable<Sobject>" or "Database.Batchable". Commented Aug 14, 2020 at 15:02

You must log in to answer this question.

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