How to mock formulas and non-writable relationships in Apex?

Reading Time: 4 minutes

In the realm of Apex development, the ability to simulate formulas and non-writable relationships opens doors to comprehensive testing and seamless troubleshooting. The dynamic nature of these elements, often pivotal in data-driven applications, can pose challenges when crafting unit tests or debugging code. This blog post delves into the art of effectively mocking formulas and non-writable relationships in Apex. By employing these strategies, developers can fortify their testing suites and enhance the robustness of their applications. So, let’s embark on a journey to unravel the techniques that empower us to conquer these intricacies and foster code that stands the test of real-world scenarios.

How can we mimic non-writable child relationships and formula fields if they are non-writable fields?

Value mocking in read-only fields 

For this exploratory project, we will continue to employ interfaces and dependency injection for convenience, but this time to create a class used to fake field values.

We’ll use a fictitious situation with parent and kid accounts, but the goal is to write a test for the Account Trigger Handler that uses a Mock Field Reader and a Mock Data Layer.

We’ll use the Mock Field Reader to simulate two non-writable fields on the Account object.

  • Child_Accounts__r, a non-writable child relationship on the parent record
  • External_URL__c, a formula field on the child record

The trigger handler logic will update the following field:

  • Parent.Latest_Child_URL__c

According to the formula field:

  • Child.External_URL__c

Documents used in this project:

  • force-app
  • ↳ main
  • ↳ default
  • ↳ classes
  • ↳ AccountTriggerHandler
  • ↳ AccountTriggerHandlerTest
  • ↳ FieldReader
  • ↳ IFieldReader
  • ↳ MockFieldReader
  • ↳ TestUtils
  • ↳ triggers
  • ↳ AccountTrigger

First, let’s create an interface with the Field Reader’s required methods:

public interface IFieldReader {
    Object getFieldValue(SObject record, String fieldName);
    SObject getFieldRecord(SObject record, String fieldName);
    List getFieldRecords(SObject record, String fieldName);
}

Create a class that reads values from a SObject record using standard SObject methods using this interface:

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 getFieldRecords(SObject record, String fieldName) { 
        return record.getSObjects(fieldName);
    }
}

Let’s also use this interface to create a class that will allow us to mock non-writable fields, such as 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;
}
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 getFieldRecords(SObject record, String fieldName) { 
    return (List)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.contains Key (record.Id)) {
           fieldToValueMap = fieldValuesByIdMap.get (record.Id);
        }
        fieldToValueMap.put(field, value);
        fieldValuesByIdMap.put(record.Id, fieldToValueMap);
     }
}

Getter

  • To honour the injected dependency, these methods must be present on both the real and mock instances of the field reader (FieldReader) (MockFieldReader).
  • getFieldValue public Object (SObject record, String fieldName)
  • This method returns the mock values of formula fields. It returns an Object that may be cast as various kinds such as String, Number, Id, and so on.
  • getFieldRecord public SObject (SObject record, String fieldName)
  • This method returns the mock values that represent a single record. It yields a SObject.
  • getFieldRecords public List (SObject record, String fieldName)
  • This method returns mock values that represent a list of records, such as a child relationship. It returns a collection of SObjects.

 

Setter

  • There is only one method for setting the values, and it is only available on the dummy instance of the field reader (MockFieldReader).
  • addValueToField(public void) (SObject record, String field, Object value)
  • The field/value pair is added to a map of mocked values, which is indexed by the record Id.

Example

AccountTriggerHandler snippet: pay attention to how the following methods are used:

getFieldRecords(parent And Child Account, 'Child Accounts r');
findFieldValue(latest Child, 'External URL c');
 // Snippet from the Account TriggerH andler
// 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 childRecords = 
fieldReader.getFieldRecords(parent And Child Account, '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.get Field Value (latestChild, 'External_URL__c');
In the Account Trigger HandlerT est, you can see how to mock these non-writable fields by using the method add Value To Field:
// Snippet from Account Trigger Handler Test
mock Data Layer = new Mock Data Layer();
mock Field Reader = new Mock Field Reader();
// 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 {mock Data Layer.child Account} 
);

Final Thoughts 

Navigating the intricacies of Apex development demands a deep understanding of its nuances, and the realm of mocking formulas and non-writable relationships is no exception. As we conclude our exploration, we have gained valuable insights into the significance of accurate mocking, the techniques to emulate formulas, and the strategies to tackle non-writable relationships. Armed with this knowledge, developers can now approach their unit testing and debugging endeavors with confidence, assured that their applications are built on a foundation of thorough examination and comprehensive simulation. By embracing these practices, we not only elevate the quality of our code but also contribute to the creation of resilient and dependable applications in the dynamic landscape of Salesforce development.

Recent Posts

Table of Contents

Share via
Copy link