In most cases, you can work around the SeeAllData=true
limitation by using the Force.com Enterprise Apex Selector pattern and ApexMocks.
here's an example for Dashboard
Your MyClass.doSomething()
method uses code such as this:
....
Dashboard[] dashboards = DashBoardsSelector.newInstance()
.selectByDeveloperName(new Set<String> {'Foo'});
The selector follows the Enterprise pattern and looks like this (excerpted):
public virtual class DashboardsSelector implements IDashboardsSelector{
public List<Schema.SObjectField> getSObjectFieldList(){
return new List<Schema.SObjectField> {
Dashboard.Description,
Dashboard.DeveloperName,
...
};
}
/* Factory to provide caller with a new Selector. Enables selectors to be mocked */
public static IDashboardsSelector newInstance() {
return (IDashboardsSelector)Application.Selector.newInstance(Dashboard.SObjectType);
}
/* getSObjectType : Used to construct queries. Required. */
public Schema.SObjectType getSObjectType() {return Dashboard.SObjectType;}
/* selectById : default selector, returns all matching SObjects for fields defined by getSObjectFieldList */
public virtual List<Dashboard> selectById(Set<Id> ids) {
return ids.isEmpty() ? new List<Dashboard> () : (Dashboard[]) selectSObjectsById(ids);
}
/* selectByDeveloperName : fetches dashboard for a devname set */
public virtual List<Dashboard> selectByDeveloperName(Set<String> devNames) {
if (devNames.isEmpty()) return new List<Dashboard> ();
fflib_QueryFactory dbQF = newQueryFactory()
.setCondition('DeveloperName IN :devNames');
return Database.query(dbQF.toSOQL());
}
}
The interface for factory and mocking is:
public interface IDashboardsSelector {
List<Dashboard> selectByDeveloperName(Set<String> devNames);
}
and your Application class has an entry for the selector (not shown here)
Now, your testmethods don't need to use SeeAllData=true
, they merely need to mock the selector and the selector's method's return:
fflib_ApexMocks mocks = new fflib_ApexMocks();
// Given mockDashboards (using Json serialize/deserialize technique - here with Sobject Fabricator github lib)
Dashboard[] mockDashboards = new List<Dashboard> ();
mockDashboards.add((Dashboard) new sfab_FabricatedSObject(Dashboard.class)
.setField(Dashboard.Id,fflib_IdGenerator.generate(Dashboard.SObjectType))
.setField(Dashboard.DeveloperName,'FooDash')
.toSObject());
// Given a mockSelector
DashboardsSelector mockDashboardsSelector = (DashboardsSelector) mocks.mock(DashboardsSelector.class);
mocks.startStubbing();
mocks.when(mockDashboardsSelector.SObjectType()).thenReturn(Dashboard.SObjectType);
for (Integer i = 0; i < mockDashboards.size(); i++) {
mocks.when(mockDashboardsSelector
.selectByDeveloperName(new Set<String> {mockDashboards[i].DeveloperName}))
.thenReturn(new List<Dashboard>{mockDashBoards[i]});
}
mocks.stopStubbing();
Application.Selector.setMock(mockDashboardsSelector);
// when codeUnderTest method invoked
new MyClass.doSomething(...);
// then verify whatever results you care about
Now this pattern is super useful and allows you to create mock results from SOQl queries against SObjects you can't otherwise insert in testmethods. The ApexMocks library gives you flexibility with selector, domain, service, and unit of work layers and can avoid creating DML actual sobjects in most of your unit tests.
But what about testing the actual selector class itself? How do you know that the method selectByDeveloperName
actually does the right thing?
Here, seeAllData=true
is required but you have localized it to just the selector class itself and the rest of your unit tests need never depend on org data
private class DashboardsSelectorTest {
@IsTest(SeeAllData=true)
static void testSelectors() {
Dashboard[] orgDashboards = [SELECT ID, DeveloperName From Dashboard Limit 1];
if (orgDashboards.isEmpty()) {return;} // can't test, no org dashboards
Dashboard[] dbs = DashboardsSelector.newInstance()
.selectByDeveloperName(new Set<String> {orgDashboards[0].DeveloperName});
System.assertEquals(1,dbs.size(),'should find the real org Dashboard');
}
}