
I’ve been trying to use some of my spare time to help my fellow Salesforce Developers in our community. And one of the best places to do so, is Salesforce Stack Exchange.
And a few days ago someone posted the following question:
How can I write a test method without actually doing the query (to speed up test run times)? As far as I know, the only way to return relationships more than one level deep is to query for them. I know ApexMock has IDGenerator but cannot see how I can use it for this use case?
Well, can we mock relationships in Apex to improve a test’s performance?
And the answer is yes, it’s possible. I’m going to explain how to achieve that in incremental steps, to try to simplify the concept.
You can mock virtually any relationship if you use a Mock Data Layer Pattern.
Imagining the scenario presented by the developer in that question, you could add an Interface to your main class. Let’s call it IDataLayer. Add this new interface to the main class (here, called DataLayerHandler.cls)
public class DataLayerHandler { | |
public interface IDataLayer { | |
List<Document__c> getDocumentList(); | |
} | |
} |
Now, in the same class, let’s create an inner class for this data layer, implementing the Interface you just created:
public class DataLayerHandler { | |
// DataLayer Class: | |
public class DataLayer implements IDataLayer { | |
List<Document__c> getDocumentList(){ | |
return [SELECT Id, Account__r.Business__c FROM Document__c]; | |
} | |
} | |
public interface IDataLayer { | |
List<Document__c> getDocumentList(); | |
} | |
} |
Ok, now how to access it? We need to create an instance of this DataLayer, and we will use the DataLayerHandler Class constructor for that. This is really important, I’ll explain it below:
public class DataLayerHandler { | |
// First, create the dataLayer variable: | |
private IDataLayer dataLayer; | |
// For an empty constructor, you create a new instance of the DataLayer: | |
public DataLayerHandler() { | |
this(new DataLayer()); | |
} | |
// If the constructor contains a dataLayer as a parameter, use it in your class. | |
public DataLayerHandler(IDataLayer dataLayer) { | |
this.dataLayer = dataLayer; | |
} | |
// Here, you can access the data using the DataLayer: | |
public void proccessDocuments() { | |
List<Document__c> documentsToProcess = dataLayer.getDocumentList(); | |
} | |
public class DataLayer implements IDataLayer { | |
List<Document__c> getDocumentList(){ | |
return [SELECT Id, Account__r.Business__c FROM Document__c]; | |
} | |
} | |
public interface IDataLayer { | |
List<Document__c> getDocumentList(); | |
} | |
} |
But why to do all of that?! And this is the magic: because now you can pass a MOCK version of the data layer to you class to use!! Given this handler class, let’s create a test class and a mock class, implementing the same interface:
@isTest | |
private class DataLayerHandler_Test { | |
private static MockDataLayer dataLayer; | |
public class MockDataLayer implements DataLayerHandler.IDataLayer { | |
List<Account> documentAccounts = new List<Account>(); | |
List<Document__c> mockDocuments = new List<Document__c>(); | |
List<Document__c> getDocumentList(){ | |
return mockDocuments; | |
} | |
} | |
} |
With the mock version of the data layer, you can start creating your mock records. Use the method getFakeId(Schema.SObjectType type) to create Ids to your mock records:
@isTest | |
private class DataLayerHandler_Test { | |
private static MockDataLayer dataLayer; | |
private static DataLayerHandler testedClass; | |
static { | |
dataLayer = new MockDataLayer(); | |
} | |
@isTest | |
private static void proccessDocumentsTest() { | |
// consider using a Mock Data Factory, but for this example, let's create a test Account: | |
Account testAccount = new Account( | |
Name = 'TestAccount', | |
Id = getFakeId(Schema.Account.SObjectType), | |
Business__c = getFakeId(Schema.Business__c.SObjectType) | |
); | |
dataLayer.documentAccounts.add(testAccount); | |
Document__c testDocument = new Document__c( | |
Name = 'TestDocument', | |
Id = getFakeId(Schema.Document__c.SObjectType), | |
Account__r = testAccount | |
); | |
dataLayer.mockDocuments.add(testDocument); | |
// Now, all you need to do is to create a new instance of you handler class, but now, passing your mock data layer to the constructor! | |
// Since the Interface structure is the same, the mock version is accepted by the class constructor. | |
Test.startTest(); | |
testedClass = new DataLayerHandler(dataLayer); | |
testedClass.proccessDocuments(); | |
Test.stopTest(); | |
//You can even use the Mock Repository to store the result of the method proccessDocuments(), to pass to your assertions. | |
} | |
//Method to generate a fake Id based on the SObject Type: | |
public static String getFakeId(Schema.SObjectType type) { | |
String result = String.valueOf(fakeIdCounter++); | |
return type.getDescribe().getKeyPrefix() + '0'.repeat(12 - result.length()) + result; | |
} | |
public class MockDataLayer implements DataLayerHandler.IDataLayer { | |
List<Account> documentAccounts = new List<Account>(); | |
List<Document__c> mockDocuments = new List<Document__c>(); | |
List<Document__c> getDocumentList(){ | |
return mockDocuments; | |
} | |
} | |
} |
There you go. This way you can mock several different types of relationships.
This is an extremely useful pattern and allows you to test your logic in different scenarios without running queries, without hitting the database. It’s a good way to improve your tests performance.
Just don’t forget to create some bulkified tests as well.
I hope that helps!