mattaddy / SObjectFabricator

An SObject fabrication API to reduce database interactions and dependencies on triggers in Apex unit tests.
Other
89 stars 14 forks source link

Relationship names in setParent are case sensitive and must match exactly to SFDC relationship name #7

Closed cropredyHelix closed 3 years ago

cropredyHelix commented 6 years ago

This took me a couple of perplexing hours to figure out today

Order[] mockOrders  = new list<Order>{

    (Order) new sfab_FabricatedSobject(Order.class)                             
      .setField(Order.Id,mockOrderIds[0])
      .setField(Order.BilltoContactId,mockContactIds[0]) . 
      .setParent('BilltoContact',new sfab_FabricatedSObject(Contact.class) // the suspect line
      .setField(Contact.Email,'foo@bar.com')
    )
};

So, inspect the parent relationship sobject BillToContact for the mockOrder[0] and ...

System.debug(mockOrders[0].BilltoContact); // returns null  ?!?

The issue is that the actual relationship name is BillToContact, not BilltoContact. As you serialize/deserialize, SFDC appears to ignore relationship names that aren't perfect match, case-wise! Although Apex is not case sensitive on SFDC object, field, or relationship names; the serialize/deserialize approach appears to be not so tolerant.

The user fix is:

Order[] mockOrders  = new list<Order>{

    (Order) new sfab_FabricatedSobject(Order.class)                             
    .setField(Order.Id,mockOrderIds[0])
    .setField(Order.BilltoContactId,mockContactIds[0]) 
    .setParent('BillToContact',new sfab_FabricatedSObject(Contact.class) // capitalize ...To...
        .setField(Contact.Email,'foo@bar.com')
        )
};

I'm not sure exactly what the right fix is:

bobalicious commented 3 years ago

Attempting to set a field or relationship that does not exist will now result in an exception.

That is, the decision was to ensure that case sensitivity is enforced.

Having tested with the following:

Order[] mockOrders= new List<Order>{
    (Order) new sfab_FabricatedSobject( Order.class )
      .setField( Order.Id, 'OrderId' )
      .setField( Order.BilltoContactId, 'ContactId' )
      .setParent( 'BilltoContact', new sfab_FabricatedSObject( Contact.class ) ) // the suspect line
      .setField( Contact.Email, 'foo@bar.com' )
      .toSObject()
};

The result is now:

sfab_FabricatedSObject.ParentRelationshipDoesNotExistException: The parent relationship Order.BilltoContact does not exist

Note, in correcting the above reference, the code now becomes:

Order[] mockOrders= new List<Order>{
    (Order) new sfab_FabricatedSobject( Order.class )
      .setField( Order.Id, 'OrderId' )
      .setField( Order.BilltoContactId, 'ContactId' )
      .setParent( 'BillToContact', new sfab_FabricatedSObject( Contact.class ) ) // the suspect line
      .setField( Contact.Email, 'foo@bar.com' )
      .toSObject()
};

This code now throws the following exception:

sfab_FabricatedSObject.FieldDoesNotExistException: The field Order.Email does not exist

This is because it is not possible to traverse a parent relationship when using SObjectField references in this way. I.E. there is no reference within the SObjectField to the object in which the Email field exists. It is not possible for Sobject Fabricator to see that the Contact has been referenced here and attach it to the correct object (incidentally, there is no correct relationship in any case). Hopefully this is now more apparent when this mistake has been made.

Note that the documentation has been updated highlight this limitation

cropredyHelix commented 3 years ago

@bobalicious

I think you might have read my example incorrectly (or I am misinterpreting your response)

    (Order) new sfab_FabricatedSobject( Order.class )
      .setField( Order.Id, 'OrderId' )
      .setField( Order.BilltoContactId, 'ContactId' )
      .setParent( 'BillToContact', new sfab_FabricatedSObject( Contact.class )  // the corrected line
            .setField( Contact.Email, 'foo@bar.com' ))   // parent Contact Email
      .toSObject()
};

unless you were trying to say that something like this now throws exception sfab_FabricatedSObject.FieldDoesNotExistException: The field Order.Email does not exist

Order[] mockOrders= new List<Order>{
    (Order) new sfab_FabricatedSobject( Order.class )
      .setField( Order.Id, 'OrderId' )
      .setField( Contact.Email, 'foo@bar.com' )  // oops, wrong SObjectType
      .toSObject()
};
bobalicious commented 3 years ago

That is correct. The last piece code as posted now throws an exception because there is no Email field on Order.

51.0 APEX_CODE,DEBUG;APEX_PROFILING,INFO
Execute Anonymous: Order[] mockOrders= new List<Order>{
Execute Anonymous:     (Order) new sfab_FabricatedSobject( Order.class )
Execute Anonymous:       .setField( Order.Id, 'OrderId' )
Execute Anonymous:       .setField( Contact.Email, 'foo@bar.com' )  // oops, wrong SObjectType
Execute Anonymous:       .toSObject()
Execute Anonymous: };
19:32:14.60 (60110619)|USER_INFO|[EXTERNAL]|00525000006yUme|xxxxxxxxxxx@example.com|(GMT+00:00) Greenwich Mean Time (Europe/London)|GMTZ
19:32:14.60 (60137497)|EXECUTION_STARTED
19:32:14.60 (60152730)|CODE_UNIT_STARTED|[EXTERNAL]|execute_anonymous_apex
19:32:14.60 (184920066)|EXCEPTION_THROWN|[362]|sfab_FabricatedSObject.FieldDoesNotExistException: The field Order.Email does not exist
19:32:14.60 (184970667)|EXCEPTION_THROWN|[4]|sfab_FabricatedSObject.FieldDoesNotExistException: The field Order.Email does not exist
19:32:14.60 (185417787)|FATAL_ERROR|sfab_FabricatedSObject.FieldDoesNotExistException: The field Order.Email does not exist
bobalicious commented 3 years ago

Apologies - I appear to have incorrectly moved a bracket when trying to get your example to compile.

A more accurate representation of your example, working, is:

Order[] mockOrders = new list<Order>{
    (Order) new sfab_FabricatedSobject(Order.class)                             
      .setField(Order.Id, 'id-1')
      .setField(Order.BilltoContactId, 'id-2' ) 
      .setParent('BillToContact',new sfab_FabricatedSObject(Contact.class)
            .setField(Contact.Email,'foo@bar.com')
        )
      .toSobject()
};

This does work.