SObject Relationship: Field is not writeable: Child__r

3k Views Asked by At

I am writing a test class for my code and am running into an issue that is annoying me. So I was wondering if there is any way to do this.

I have an Apex class with some functionality that I am Unit Testing, let's assume it is defined something like this:

public class HelperClass {
    public static Object someFunction(Parent__c parentObject) {}
}

The parentObject SObject input is expected to be provided in such a way, that it will contain the parent record as well as the child records so they are passed as one input and stay together. This is done by the calling methods by using a relationship query similar to the following way:

Parent__c parentObject = [SELECT Id, Name, (SELECT Id, Name FROM Children__r) WHERE Id = :recId];

Parent__c and Child__c are both configuration objects so for testing I have a pretty good idea what they should typically look like to fit my test cases. I want to see if it is possible to skip doing the DML as I want fast test classes and it are quite a few records that I want to create for all assertions. So I wanted to build the test data as below:

@IsTest
private static void testHelperClassSomeFunction() {    
    // define children records
    List<Child__c> testChildren = new List<Child__c>{new Child__c(Name = 'Test Child Record')};    
    
    // define parent record
    Parent__c testParent = new Parent__c(Name = 'Test Parent Record');
    // add children records to the relationship:
    testParent.Children__r = testChildren;

    Object testOutput = HelperClass.somefunction(testParent);    
    // asserts here    
}

However, if I do this, I get the error

Field is not writeable: Parent__c.Children__r

I know that I can insert the children and the parent and then use a relationship query. As mentioned I want to keep from doing the DML and requerying the records as this would otherwise be needlessly slow and this will be repeated in other tests as well.

Plus, I just want to know if there is any way around this... So is there any way to build the object as described above in Apex without fetching the data using a query?

1

There are 1 best solutions below

0
On

You can technically create a parent SObject with related child SObjects using a JSON workaround. I got the implementation from a Cloud Giants' blog that uses a roundtrip JSON serialization to manually add the required metadata for child SObjects. The blog dives a bit deeper into n-deep relationships, but the gist is this:

  1. Serialize the parent sObject into JSON:
    String parentJson = JSON.serialize(testParent);
    
  2. Manually create the JSON metadata for the child sObjects:
    String childrenJson = '"Children__r": {'
        + '"totalSize": ' + testChildren.size() + ', '
        + '"done": true,'
        + '"records": ' + JSON.serialize(testChildren)
        + '}';
    
  3. Merge the children's JSON metadata onto the end the parent's JSON:
    parentJson = parentJson.substring(0, parentJson.length() - 1) + ','
        + childrenJson
        + '}';
    
  4. Deserialize the JSON metadata back into an SObject:
    (Parent__c) JSON.deserialize(parentJson, Parent__c.class);
    

I ended up using the "generic" SObject in my helper method to allow me to use it across multiple tests.

private static SObject hydrateChildRecordList(String relationshipName, SObject parent, List<SObject> children) {
    String recordJson = JSON.serialize(parent);
    String childrenJson = '"' + relationshipName + '": {'
        + '"totalSize": ' + children.size() + ', '
        + '"done": true,'
        + '"records": ' + JSON.serialize(children)
        + '}';
    recordJson = recordJson.substring(0, recordJson.length() - 1) + ',' + childrenJson + '}';

    return (SObject) JSON.deserialize(recordJson, SObject.class);
}

You can also checkout the blog mentioned above for a much more sophisticated utility if you need it.