Assume we have some basic AccountSelector
class that has different implementations of the selector method. QueryLocator variation is helpful once we need to take advantage of SOQL for loops or to use it in the start()
method of batches.
public with sharing class AccountSelector extends fflib_SObjectSelector {
public override SObjectType getSObjectType() {
return Account.SObjectType;
}
public override List<SObjectField> getSObjectFieldList() {
return new List<SObjectField>{
Account.Id,
Account.Name
};
}
public List<Account> byIds(Set<Id> ids) {
return (List<Account>) Database.query(soqlByIds(ids));
}
public Database.QueryLocator locatorByIds(Set<Id> ids) {
return Database.getQueryLocator(soqlByIds(ids));
}
private String soqlByIds(Set<Id> ids) {
fflib_QueryFactory queryFactory = newQueryFactory()
.setEnforceFLS(true)
.setCondition('Id IN :ids');
return queryFactory.toSOQL();
}
}
Some service class uses AccountSelector
and I need to unit test that service class using fflib Apex Mocks:
@IsTest(IsParallel=true)
private class SomeServiceUnitTest {
@IsTest
static void shouldReturnAccount() {
// Create Mocks
fflib_ApexMocks mocks = new fflib_ApexMocks();
AccountSelector accSelectorMock = (AccountSelector) mocks.mock(AccountSelector.class);
// Given
List<Account> accountsMock = new List<Account>();
Id accId = fflib_IDGenerator.generate(Account.SObjectType);
accountsMock.add(
new Account(
Id = accId,
Name = 'TestName'
)
);
mocks.startStubbing();
mocks.when(accSelectorMock.sObjectType())
.thenReturn(Account.SObjectType);
mocks.when(accSelectorMock.byIds(new Set<Id>{ accId }))
.thenReturn(accountsMock); // Works as expected
mocks.when(accSelectorMock.locatorByIds(new Set<Id>{ accId }))
.thenReturn(???); // Need to get/create an instance of Database.QueryLocator to mock
mocks.stopStubbing();
Application.SELECTOR.setMock(accSelectorMock);
}
}
So the problem here is that when we want to mock accSelectorMock.locatorByIds()
method invocation, we need to get an instance of Database.QueryLocator
. The known way to get it is Database.getQueryLocator()
static method which can take either String query
or List<SObject> query
param e.g:
Database.getQueryLocator('SELECT Id FROM Account');
Database.getQueryLocator([SELECT Id FROM Account]);
Database.QueryLocator
instance represents a server-side database cursor but in case if we want to write pure unit tests without any DML operations(e.g. inserting test accounts) it seems impossible to get a mock of Database.QueryLocator
instance somehow.
So the questions are:
Can we get an instance of
Database.QueryLocator
that points to some test records without first inserting them?
How to mock a selector method that returns
Database.QueryLocator
in the unit tests in terms offflib Apex Mocks
?
Iterable
... List<Sobject> implements iterable so you could mock a list of sobjects; if application need is for batchable, I've done what @willard answeredIterator
norIterable
cannot be mocked as return types or parameter types due to the limitations developer.salesforce.com/docs/atlas.en-us.apexcode.meta/…