diff --git a/sfdx-source/apex-mocks/main/classes/fflib_ApexMocksUtils.cls b/sfdx-source/apex-mocks/main/classes/fflib_ApexMocksUtils.cls index 3709b96c..5976e4e8 100644 --- a/sfdx-source/apex-mocks/main/classes/fflib_ApexMocksUtils.cls +++ b/sfdx-source/apex-mocks/main/classes/fflib_ApexMocksUtils.cls @@ -110,13 +110,12 @@ public class fflib_ApexMocksUtils * values on read-only fields by their name */ public static Object setReadOnlyFields(SObject objInstance, Type deserializeType, Map properties) { + JSONParser parser = JSON.createParser((JSON.serialize(objInstance))); + JSONGenerator combinedOutput = JSON.createGenerator(false); - Map mergedMap = new Map(objInstance.getPopulatedFieldsAsMap()); - // Merge the values from the properties map into the fields already set on the object - mergedMap.putAll(properties); - // Serialize the merged map, and then deserialize it as the desired object type. - String jsonString = JSON.serializePretty(mergedMap); - return (SObject) JSON.deserialize(jsonString, deserializeType); + // parse the source object and inject new fields into the resulting JSON + streamTokens(parser, combinedOutput, new InjectFieldsEventHandler(properties) ); + return JSON.deserialize(combinedOutput.getAsString(), deserializeType); } /** @@ -183,6 +182,29 @@ public class fflib_ApexMocksUtils } } + /** + * Monitors stream events for end of object for the current SObject + * then injects new fields into the stream + */ + private class InjectFieldsEventHandler implements JSONParserEvents + { + Map properties; + + public InjectFieldsEventHandler(Map properties) { + this.properties = properties; + } + + public void nextToken(JSONParser fromStream, Integer depth, JSONGenerator toStream) { + // Inject children? + JSONToken currentToken = fromStream.getCurrentToken(); + if(depth == 1 && currentToken == JSONToken.END_OBJECT ) { + for(String field : properties.keySet()) { + toStream.writeObjectField(field, properties.get(field)); + } + } + } + } + /** * Utility function to stream tokens from a reader to a write, while providing a basic eventing model */ diff --git a/sfdx-source/apex-mocks/test/classes/fflib_ApexMocksUtilsTest.cls b/sfdx-source/apex-mocks/test/classes/fflib_ApexMocksUtilsTest.cls index cf613070..b21610d0 100644 --- a/sfdx-source/apex-mocks/test/classes/fflib_ApexMocksUtilsTest.cls +++ b/sfdx-source/apex-mocks/test/classes/fflib_ApexMocksUtilsTest.cls @@ -337,4 +337,47 @@ public class fflib_ApexMocksUtilsTest System.Assert.areEqual(acc.Name, t.What.Name); } + + @isTest + static void setReadOnlyFields_WithChildren_FieldSetSuccessfully() { + // Given + Contact cont = new Contact( + Id = fflib_IDGenerator.generate(Contact.SObjectType), + LastName = 'ContactName' + ); + + Opportunity opp = new Opportunity( + Id = fflib_IDGenerator.generate(Opportunity.SObjectType), + Name = 'OpportunityName' + ); + + Account acc = new Account(); + + Id userId = fflib_IDGenerator.generate((new User()).getSObjectType()); + + acc = ((List) fflib_ApexMocksUtils.makeRelationship( + List.class, + new List{ acc }, + Contact.AccountId, + new List>{ new List{cont} } + ))[0]; + + acc = ((List) fflib_ApexMocksUtils.makeRelationship( + List.class, + new List{ acc }, + Opportunity.AccountId, + new List>{ new List{opp} } + ))[0]; + + // When + Test.startTest(); + acc = (Account)fflib_ApexMocksUtils.setReadOnlyFields( + acc, + Account.class, + new Map{Account.CreatedById => userId} + ); + Test.stopTest(); + + Assert.areEqual(userId, acc.CreatedById); + } }