How to mock formulas and read-only relationships in Apex tests?
Use dependency injection to mock read-only fields like formula fields and child relationships in your Apex tests.

⚙️ Repository on Github: MockFieldReader.
A while ago we explored the Mock Data Layer Pattern, which allows us to mock virtually any relationships between records within your tests. This is especially handy when trying to improve the performance of these tests, more details here on matheus.dev.
Now, how can we mock formula fields and non-writable child relationships if they are, well, non-writable fields?
Mocking values in read-only fields
For this exploratory project, we will continue using the convenience of interfaces and dependency injection, but this time to develop a class used to mock field values.
We will use a not-so-realistic scenario with parent and child accounts, but the intent is to create a test for the Account Trigger Handler that will use a Mock Field Reader and a Mock Data Layer.
The Mock Field Reader will be used so we can mock two non-writable fields on the Account object.
Child_Accounts__r, a non-writable child relationship on the parent recordExternal_URL__c, a formula field on the child record
The trigger handler logic will update the following field:
Parent.Latest_Child_URL__c
Based on the following formula field:
Child.External_URL__c
Files used in this project
force-app
└── main
└── default
├── classes
│ ├── AccountTriggerHandler
│ ├── AccountTriggerHandlerTest
│ ├── FieldReader
│ ├── IFieldReader
│ ├── MockFieldReader
│ └── TestUtils
└── triggers
└── AccountTrigger
Show me the code!
First, let’s create an interface with the required methods for the Field Reader:
public interface IFieldReader {
Object getFieldValue(SObject record, String fieldName);
SObject getFieldRecord(SObject record, String fieldName);
List<SObject> getFieldRecords(SObject record, String fieldName);
}
Using this interface, let’s create a class that reads values from a SObject record, using standard SObject methods:
public class FieldReader implements IFieldReader {
// Retrieves a value from a field, as an object
public Object getFieldValue(SObject record, String fieldName) {
return record.get(fieldName);
}
// Retrieves a value from a field, as a SObject
public SObject getFieldRecord(SObject record, String fieldName) {
return record.getSObject(fieldName);
}
// Retrieves a value from a field, as a List of SObject
public List<SObject> getFieldRecords(SObject record, String fieldName) {
return record.getSObjects(fieldName);
}
}
Also, using this interface, let’s create a class that will allow us to mock the non-writable fields, like a child relationship or formula fields:
public class MockFieldReader implements IFieldReader {
// Map of value stored in a field by Id
private Map<Id, Map<String, Object>> fieldValuesByIdMap = new Map<Id, Map<String, Object>>();
// ======================================================================
// GETTERS
// The following method will try to find the field name and value
// in the map above, and return the correct value accordingly
// ======================================================================
public Object getFieldValue(SObject record, String fieldName) {
Map<String, Object> fieldToValueMap = new Map<String, Object>();
// If a record Id was provided,
// attempts to retrive the field/value pair from the map
if (record?.Id != null)
fieldToValueMap = fieldValuesByIdMap.get(record.Id);
// If the field/value pair was retrieved
// returns the value based on the field
Object result = (fieldToValueMap == null)
? null
: fieldToValueMap.get(fieldName);
return result;
}
// Get a value of a field as an SObject
public SObject getFieldRecord(SObject record, String fieldName) {
return (SObject)getFieldValue(record, fieldName);
}
// Get a value of a field as a List of SObjects
public List<SObject> getFieldRecords(SObject record, String fieldName) {
return (List<SObject>)getFieldValue(record, fieldName);
}
// ======================================================================
// SETTER
// The following method is used to add values and fields in the map
// ======================================================================
// Adds a value to a field by Id
public void addValueToField(SObject record, String field, Object value) {
Map<String, Object> fieldToValueMap = new Map<String, Object>();
if (record != null) {
if (fieldValuesByIdMap.containsKey(record.Id)) {
fieldToValueMap = fieldValuesByIdMap.get(record.Id);
}
fieldToValueMap.put(field, value);
fieldValuesByIdMap.put(record.Id, fieldToValueMap);
}
}
}
Getters
To respect the injected dependency, these methods must be present on both the real instance of the field reader (FieldReader) and the mock instance of the field reader (MockFieldReader).
public Object getFieldValue(SObject record, String fieldName)
This method is used to retrieve the mock values of formula fields. It returns an Object that can later be cast as different types, such as String, Number, Id, etc.
public SObject getFieldRecord(SObject record, String fieldName)
This method is used to retrieve the mock values that represent a single record. It returns an SObject.
public List getFieldRecords(SObject record, String fieldName)
This method is used to retrieve the mock values that represent a list of records, such as a child relationship. It returns a list of SObjects.
Setter
There’s only one method used to set the values, and it’s only present on the mock instance of the field reader (MockFieldReader).
public void addValueToField(SObject record, String field, Object value)
This method adds the field/value pair to a map of mocked values, indexed by the record Id.
Examples
In the repository on Github you will find a trigger handler class and its respective test class with the usage of this Mock Field Reader.
Snippet from the AccountTriggerHandler: pay attention to how the following methods are being used:
fieldReader.getFieldRecords(parentAndChildAccount, 'Child_Accounts__r');
fieldReader.getFieldValue(latestChild, 'External_URL__c');
// Snippet from the AccountTriggerHandler
...
// If the mock data is not being used
// creates new instances of the real deal
public AccountTriggerHandler() {
this(new DataLayer(), new FieldReader());
}
// Child_Accounts__r is a nonwritable child relationship
// Uses the field reader to retrieve the list of child records:
List<Account> childRecords =
fieldReader.getFieldRecords(parentAndChildAccount, 'Child_Accounts__r');
// Updates the field Latest Child URL with the value from External_URL__c
// External_URL__c is a formula field (nonwritable string)
newRecord.Latest_Child_URL__c =
(String)fieldReader.getFieldValue(latestChild, 'External_URL__c');
And in the AccountTriggerHandlerTest you can see how these non-writable fields can be mocked, using the method addValueToField:
// Snippet from AccountTriggerHandlerTest
...
mockDataLayer = new MockDataLayer();
mockFieldReader = new MockFieldReader();
...
// First, the formula field on the child object:
mockFieldReader.addValueToField(
mockDataLayer.childAccount,
'External_URL__c',
expectedURL
);
// Then, the nonwritable child relationship
mockFieldReader.addValueToField(
mockDataLayer.parentAccount,
'Child_Accounts__r',
new List<Account>{mockDataLayer.childAccount}
);
With this code and the Mock Field Reader, it becomes easy to mock non-writable fields used in the tests.
I hope that helps!