diff --git a/sfdx-source/apex-common/main/classes/fflib_ISObjectUnitOfWork.cls b/sfdx-source/apex-common/main/classes/fflib_ISObjectUnitOfWork.cls index a18e1256f40..5e89300a4fb 100644 --- a/sfdx-source/apex-common/main/classes/fflib_ISObjectUnitOfWork.cls +++ b/sfdx-source/apex-common/main/classes/fflib_ISObjectUnitOfWork.cls @@ -50,6 +50,15 @@ public interface fflib_ISObjectUnitOfWork * @param relatedToParentRecord A SObject instance of the parent record (should also be registered as new separately) **/ void registerNew(SObject record, Schema.SObjectField relatedToParentField, SObject relatedToParentRecord); + /** + * Register newly created records to be inserted when commitWork is called, + * it also provides a reference to the single parent record instance (should also be registered as new separately) + * + * @param records newly created records of the same SObjectType to be inserted during commitWork + * @param relatedToParentField A SObjectField reference to the child field that associates the child record with its parent + * @param relatedToParentRecord A SObject instance of the parent record (should also be registered as new separately) + **/ + void registerNew(List records, Schema.SObjectField relatedToParentField, SObject relatedToParentRecord); /** * Register a relationship between two records that have yet to be inserted to the database. This information will be * used during the commitWork phase to make the references only when related records have been inserted to the database. @@ -59,6 +68,15 @@ public interface fflib_ISObjectUnitOfWork * @param relatedTo A SObject instance (yet to be committed to the database) */ void registerRelationship(SObject record, Schema.SObjectField relatedToField, SObject relatedTo); + /** + * Register a relationship between two records that have yet to be inserted to the database. This information will be + * used during the commitWork phase to make the references only when related records have been inserted to the database. + * + * @param records Existing or newly created records + * @param relatedToField A SObjectField reference to the field that relates the given records to the relatedTo record + * @param relatedTo A SObject instance (yet to be committed to the database) + */ + void registerRelationship(List records, Schema.SObjectField relatedToField, SObject relatedTo); /** * Registers a relationship between a record and a Messaging.Email where the record has yet to be inserted * to the database. This information will be diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls index 28feb2d451a..fd3c4d387cd 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls @@ -285,37 +285,49 @@ public virtual class fflib_SObjectUnitOfWork String sObjectName = sObjectType.getDescribe().getName(); assertForNonEventSObjectType(sObjectName); assertForSupportedSObjectType(m_newListByType, sObjectName); + assertForNewRecordsWithSameSObjectType(records, sObjectType); - for (SObject record : records) - { - if (record.getSObjectType() != sObjectType) - throw new UnitOfWorkException('All records should be of the same SObjectType'); - if (record.Id != null) - throw new UnitOfWorkException('Only new records can be registered as new'); + m_newListByType.get(sObjectName).addAll(records); - m_newListByType.get(sObjectName).add(record); - if (relatedToParentRecord != null && relatedToParentField != null) - registerRelationship(record, relatedToParentField, relatedToParentRecord); - } + if (relatedToParentRecord == null || relatedToParentField == null) return; + + registerRelationship(records, relatedToParentField, relatedToParentRecord); } - /** - * Register a relationship between two records that have yet to be inserted to the database. This information will be - * used during the commitWork phase to make the references only when related records have been inserted to the database. - * - * @param record An existing or newly created record - * @param relatedToField A SObjectField reference to the lookup field that relates the two records together - * @param relatedTo A SObject instance (yet to be committed to the database) - */ - public void registerRelationship(SObject record, Schema.SObjectField relatedToField, SObject relatedTo) - { - String sObjectType = record.getSObjectType().getDescribe().getName(); + /** + * Register a relationship between two records that have yet to be inserted to the database. This information will be + * used during the commitWork phase to make the references only when related records have been inserted to the database. + * + * @param record An existing or newly created record + * @param relatedToField A SObjectField reference to the lookup field that relates the two records together + * @param relatedTo A SObject instance (yet to be committed to the database) + */ + public void registerRelationship(SObject record, Schema.SObjectField relatedToField, SObject relatedTo) + { + registerRelationship(new List {record}, relatedToField, relatedTo); + } - assertForNonEventSObjectType(sObjectType); - assertForSupportedSObjectType(m_newListByType, sObjectType); + /** + * Register a relationship between two records that have yet to be inserted to the database. This information will be + * used during the commitWork phase to make the references only when related records have been inserted to the database. + * + * @param records Existing or newly created records + * @param relatedToField A SObjectField reference to the field that relates the given records to the relatedTo record + * @param relatedTo A SObject instance (yet to be committed to the database) + */ + public void registerRelationship(List records, Schema.SObjectField relatedToField, SObject relatedTo) + { + if (records.isEmpty()) return; - m_relationships.get(sObjectType).add(record, relatedToField, relatedTo); - } + // Only validate the first record, all records should of the same type. + Schema.SObjectType sObjectType = records.get(0).getSObjectType(); + String sObjectName = sObjectType.getDescribe().getName(); + assertForNonEventSObjectType(sObjectName); + assertForSupportedSObjectType(m_newListByType, sObjectName); + assertForSameSObjectType(records, sObjectType); + + m_relationships.get(sObjectName).add(records, relatedToField, relatedTo); + } /** * Registers a relationship between a record and a Messaging.Email where the record has yet to be inserted @@ -348,7 +360,7 @@ public virtual class fflib_SObjectUnitOfWork // NOTE: Due to the lack of ExternalID references on Standard Objects, this method can not be provided a standardized Unit Test. - Rick Parker String sObjectType = record.getSObjectType().getDescribe().getName(); if(!m_newListByType.containsKey(sObjectType)) - throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType })); + throw new UnitOfWorkException(String.format(Label.fflib_SObjectUnitOfWork_not_supported_SObjectType, new String[] { sObjectType })); m_relationships.get(sObjectType).add(record, relatedToField, externalIdField, externalId); } @@ -362,52 +374,66 @@ public virtual class fflib_SObjectUnitOfWork registerDirty(record, new List()); } - /** - * Registers the entire records as dirty or just only the dirty fields if the record was already registered - * - * @param records SObjects to register as dirty - * @param dirtyFields A list of modified fields - */ - public void registerDirty(List records, List dirtyFields) - { - for (SObject record : records) - { - registerDirty(record, dirtyFields); - } - } - /** * Registers the entire record as dirty or just only the dirty fields if the record was already registered * * @param record SObject to register as dirty * @param dirtyFields A list of modified fields */ - public void registerDirty(SObject record, List dirtyFields) - { - if (record.Id == null) - throw new UnitOfWorkException('New records cannot be registered as dirty'); - String sObjectType = record.getSObjectType().getDescribe().getName(); + public void registerDirty(SObject record, List dirtyFields) + { + registerDirty(new List {record}, dirtyFields); + } - assertForNonEventSObjectType(sObjectType); - assertForSupportedSObjectType(m_dirtyMapByType, sObjectType); + /** + * Registers the entire record as dirty or just only the dirty fields if the record was already registered + * + * @param record SObject to register as dirty + * @param dirtyFields A list of modified fields + */ + public void registerDirty(List records, List dirtyFields) + { + if (records.isEmpty()) return; - // If record isn't registered as dirty, or no dirty fields to drive a merge - if (!m_dirtyMapByType.get(sObjectType).containsKey(record.Id) || dirtyFields.isEmpty()) - { - // Register the record as dirty - m_dirtyMapByType.get(sObjectType).put(record.Id, record); - } - else - { - // Update the registered record's fields - SObject registeredRecord = m_dirtyMapByType.get(sObjectType).get(record.Id); + // Only validate the first record, all records should be existing and of the same type. + Schema.SObjectType sObjectType = records.get(0).getSObjectType(); + String sObjectName = sObjectType.getDescribe().getName(); - for (SObjectField dirtyField : dirtyFields) { - registeredRecord.put(dirtyField, record.get(dirtyField)); - } + assertForNonEventSObjectType(sObjectName); + assertForSupportedSObjectType(m_dirtyMapByType, sObjectName); + assertForExistingRecordsWithSameSObjectType(records, sObjectType); - m_dirtyMapByType.get(sObjectType).put(record.Id, registeredRecord); - } + // Register the record as dirty, if no dirty fields or all records are not registered as dirty + if (dirtyFields.isEmpty() + || + !m_dirtyMapByType.get(sObjectName).keySet().containsAll(new Map(records).keySet())) + { + m_dirtyMapByType.get(sObjectName).putAll(records); + } + else + { + // Check records one by one and resolve dirty fields. + for (SObject record : records) + { + // Register the record as dirty, if record isn't registered already + if (!m_dirtyMapByType.get(sObjectName).containsKey(record.Id)) + { + m_dirtyMapByType.get(sObjectName).put(record.Id, record); + } + else + { + // Update the registered record's fields + SObject registeredRecord = m_dirtyMapByType.get(sObjectName).get(record.Id); + + for (SObjectField dirtyField : dirtyFields) + { + registeredRecord.put(dirtyField, record.get(dirtyField)); + } + + m_dirtyMapByType.get(sObjectName).put(record.Id, registeredRecord); + } + } + } } /** @@ -420,9 +446,7 @@ public virtual class fflib_SObjectUnitOfWork **/ public void registerDirty(SObject record, Schema.SObjectField relatedToParentField, SObject relatedToParentRecord) { - registerDirty(record); - if (relatedToParentRecord!=null && relatedToParentField!=null) - registerRelationship(record, relatedToParentField, relatedToParentRecord); + registerDirty(new List {record}, relatedToParentField, relatedToParentRecord); } /** @@ -442,18 +466,13 @@ public virtual class fflib_SObjectUnitOfWork String sObjectName = sObjectType.getDescribe().getName(); assertForNonEventSObjectType(sObjectName); assertForSupportedSObjectType(m_dirtyMapByType, sObjectName); + assertForExistingRecordsWithSameSObjectType(records, sObjectType); - for (SObject record : records) - { - if (record.getSObjectType() != sObjectType) - throw new UnitOfWorkException('All records should be of the same SObjectType'); - if (record.Id == null) - throw new UnitOfWorkException('New records cannot be registered as dirty'); + m_dirtyMapByType.get(sObjectName).putAll(records); - m_dirtyMapByType.get(sObjectName).put(record.Id, record); - if (relatedToParentRecord != null && relatedToParentField != null) - registerRelationship(record, relatedToParentField, relatedToParentRecord); - } + if (relatedToParentRecord == null || relatedToParentField == null) return; + + registerRelationship(records, relatedToParentField, relatedToParentRecord); } /** @@ -463,10 +482,7 @@ public virtual class fflib_SObjectUnitOfWork **/ public void registerDirty(List records) { - for (SObject record : records) - { - this.registerDirty(record); - } + registerDirty(records, new List()); } /** @@ -476,14 +492,7 @@ public virtual class fflib_SObjectUnitOfWork **/ public void registerUpsert(SObject record) { - if (record.Id == null) - { - registerNew(record, null, null); - } - else - { - registerDirty(record, new List()); - } + registerUpsert(new List {record}); } /** @@ -493,10 +502,15 @@ public virtual class fflib_SObjectUnitOfWork **/ public void registerUpsert(List records) { - for (SObject record : records) - { - this.registerUpsert(record); - } + List forInsert = new List(); + List forUpdate = new List(); + for (SObject record : records) + { + if (record.Id == null) forInsert.add(record); + else forUpdate.add(record); + } + if (!forInsert.isEmpty()) registerNew(forInsert); + if (!forUpdate.isEmpty()) registerDirty(forUpdate); } /** @@ -507,7 +521,7 @@ public virtual class fflib_SObjectUnitOfWork public void registerDeleted(SObject record) { if (record.Id == null) - throw new UnitOfWorkException('New records cannot be registered for deletion'); + throw new UnitOfWorkException(Label.fflib_SObjectUnitOfWork_new_records_not_supported_for_deletion); String sObjectType = record.getSObjectType().getDescribe().getName(); assertForNonEventSObjectType(sObjectType); @@ -784,9 +798,7 @@ public virtual class fflib_SObjectUnitOfWork if (sObjectType.length() > 3 && sObjectType.right(3) == '__e') { throw new UnitOfWorkException( - String.format( - 'SObject type {0} must use registerPublishBeforeTransaction or ' + - 'registerPublishAfterTransaction methods to be used within this unit of work', + String.format(Label.fflib_SObjectUnitOfWork_event_SObjectType_must_use_publish, new List { sObjectType } ) ); @@ -799,14 +811,47 @@ public virtual class fflib_SObjectUnitOfWork if (sObjectType.length() > 3 && sObjectType.right(3) != '__e') { throw new UnitOfWorkException( - String.format( - 'SObject type {0} is invalid for publishing within this unit of work', + String.format(Label.fflib_SObjectUnitOfWork_non_event_SObjectType_used_with_publish, new List {sObjectType} ) ); } } + private void assertForExistingRecordsWithSameSObjectType(List records, Schema.SObjectType sObjectType) + { + for (SObject record : records) + { + if (record.Id == null) + throw new UnitOfWorkException(Label.fflib_SObjectUnitOfWork_new_records_not_supported_for_update); + + if (record.getSObjectType() != sObjectType) + throw new UnitOfWorkException(Label.fflib_SObjectUnitOfWork_require_same_SObjectType); + } + } + + private void assertForNewRecordsWithSameSObjectType(List records, Schema.SObjectType sObjectType) + { + for (SObject record : records) + { + if (record.Id != null) + throw new UnitOfWorkException(Label.fflib_SObjectUnitOfWork_only_new_records_supported); + + if (record.getSObjectType() != sObjectType) + throw new UnitOfWorkException(Label.fflib_SObjectUnitOfWork_require_same_SObjectType); + } + } + + private void assertForSameSObjectType(List records, Schema.SObjectType sObjectType) + { + for (SObject record : records) + { + if (record.getSObjectType() == sObjectType) continue; + + throw new UnitOfWorkException('All records should be of the same SObjectType'); + } + } + @TestVisible private void assertForSupportedSObjectType(Map theMap, String sObjectType) { @@ -814,7 +859,7 @@ public virtual class fflib_SObjectUnitOfWork { throw new UnitOfWorkException( String.format( - 'SObject type {0} is not supported by this unit of work', + Label.fflib_SObjectUnitOfWork_not_supported_SObjectType, new List { sObjectType } ) ); @@ -839,12 +884,12 @@ public virtual class fflib_SObjectUnitOfWork public void add(SObject record, Schema.SObjectField relatedToField, Schema.SObjectField externalIdField, Object externalId) { if (relatedToField == null) { - throw new UnitOfWorkException('Invalid argument: relatedToField.'); + throw new UnitOfWorkException(Label.fflib_SObjectUnitOfWork_invalid_argument_relatedToField_null); } String relationshipName = relatedToField.getDescribe().getRelationshipName(); if (String.isBlank(relationshipName)) { - throw new UnitOfWorkException('Invalid argument: relatedToField. Field supplied is not a relationship field.'); + throw new UnitOfWorkException(Label.fflib_SObjectUnitOfWork_invalid_argument_relatedToField_invalid); } List relatedObjects = relatedToField.getDescribe().getReferenceTo(); @@ -855,32 +900,40 @@ public virtual class fflib_SObjectUnitOfWork Boolean externalIdFieldIsValid = externalIdField.getDescribe().isExternalId(); if (!relatedHasExternalIdField) { - throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.'); + throw new UnitOfWorkException(Label.fflib_SObjectUnitOfWork_invalid_argument_externalIdField_unknown); } if (!externalIdFieldIsValid) { - throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a marked as an External Identifier.'); + throw new UnitOfWorkException(Label.fflib_SObjectUnitOfWork_invalid_argument_externalIdField_invalid); } - RelationshipByExternalId relationship = new RelationshipByExternalId(); - relationship.Record = record; - relationship.RelatedToField = relatedToField; - relationship.RelatedTo = relatedObject; - relationship.RelationshipName = relationshipName; - relationship.ExternalIdField = externalIdField; - relationship.ExternalId = externalId; - m_relationships.add(relationship); + RelationshipByExternalId relationship = new RelationshipByExternalId(); + relationship.Record = record; + relationship.RelatedToField = relatedToField; + relationship.RelatedTo = relatedObject; + relationship.RelationshipName = relationshipName; + relationship.ExternalIdField = externalIdField; + relationship.ExternalId = externalId; + m_relationships.add(relationship); } - public void add(SObject record, Schema.SObjectField relatedToField, SObject relatedTo) - { - // Relationship to resolve - Relationship relationship = new Relationship(); - relationship.Record = record; - relationship.RelatedToField = relatedToField; - relationship.RelatedTo = relatedTo; - m_relationships.add(relationship); - } + public void add(List records, Schema.SObjectField relatedToField, SObject relatedTo) + { + for (SObject record : records) + { + add(record, relatedToField, relatedTo); + } + } + + public void add(SObject record, Schema.SObjectField relatedToField, SObject relatedTo) + { + // Relationship to resolve + Relationship relationship = new Relationship(); + relationship.Record = record; + relationship.RelatedToField = relatedToField; + relationship.RelatedTo = relatedTo; + m_relationships.add(relationship); + } public void add(Messaging.SingleEmailMessage email, SObject relatedTo) { diff --git a/sfdx-source/apex-common/main/labels/fflib-Apex-Common-CustomLabels.labels-meta.xml b/sfdx-source/apex-common/main/labels/fflib-Apex-Common-CustomLabels.labels-meta.xml index d4002b0a0d3..085345765ce 100644 --- a/sfdx-source/apex-common/main/labels/fflib-Apex-Common-CustomLabels.labels-meta.xml +++ b/sfdx-source/apex-common/main/labels/fflib-Apex-Common-CustomLabels.labels-meta.xml @@ -1,5 +1,82 @@ + + fflib_SObjectUnitOfWork_event_SObjectType_must_use_publish + en_US + false + Error when trying use non-publish method on Event SObjectTypes. + SObject type {0} must use registerPublishBeforeTransaction or registerPublishAfterTransaction methods to be used within this unit of work + + + fflib_SObjectUnitOfWork_invalid_argument_externalIdField_invalid + en_US + false + Error when the provided externalIdField is not an external Id. + Invalid argument: externalIdField. Field supplied is not a marked as an External Identifier. + + + fflib_SObjectUnitOfWork_invalid_argument_externalIdField_unknown + en_US + false + Error when the provided externalIdField is not available on the SObjectType. + Invalid argument: externalIdField. Field supplied is not a known field on the target sObject. + + + fflib_SObjectUnitOfWork_invalid_argument_relatedToField_null + en_US + false + Error when relatedToField argument is null. + Invalid argument: relatedToField. + + + fflib_SObjectUnitOfWork_invalid_argument_relatedToField_invalid + en_US + false + Error when relatedToField argument is not a relationship field. + Invalid argument: relatedToField. Field supplied is not a relationship field. + + + fflib_SObjectUnitOfWork_new_records_not_supported_for_deletion + en_US + false + Error when trying to add new records for deletion. + New records cannot be registered for deletion + + + fflib_SObjectUnitOfWork_new_records_not_supported_for_update + en_US + false + Error when trying to add new records as dirty. + New records cannot be registered as dirty + + + fflib_SObjectUnitOfWork_non_event_SObjectType_used_with_publish + en_US + false + Error when trying use publish method with a non-Event SObjectType. + SObject type {0} is invalid for publishing within this unit of work + + + fflib_SObjectUnitOfWork_not_supported_SObjectType + en_US + false + Error when adding records of a not supported SObjectType to the UnitOfWork. + SObject type {0} is not supported by this unit of work + + + fflib_SObjectUnitOfWork_only_new_records_supported + en_US + false + Error when adding existing records as new. + Only new records can be registered + + + fflib_SObjectUnitOfWork_require_same_SObjectType + en_US + false + Error when adding records of different SObjectTypes. + All records should be of the same SObjectType + fflib_QueryFactory_crossobject_fieldsets_not_allowed_error en_US diff --git a/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls b/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls index 06274a9f38f..8de465e7c7c 100644 --- a/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls +++ b/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls @@ -83,9 +83,8 @@ private with sharing class fflib_SObjectUnitOfWorkTest catch (Exception e) { exceptionThrown = true; - System.assertEquals( - 'Only new records can be registered as new', - e.getMessage(), + System.assert( + e.getMessage().contains(Label.fflib_SObjectUnitOfWork_only_new_records_supported), 'Incorrect exception message thrown' ); } @@ -94,6 +93,53 @@ private with sharing class fflib_SObjectUnitOfWorkTest System.assert(exceptionThrown); } + @IsTest + private static void testRegisterNew_Bulk() + { + List records = new List + { + new Opportunity(Name = 'A'), + new Opportunity(Name = 'B'), + new Opportunity(Name = 'C') + }; + + Test.startTest(); + MockDML mockDML = new MockDML(); + fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(MY_SOBJECTS, mockDML); + uow.registerNew(records); + uow.commitWork(); + Test.stopTest(); + + System.assertEquals(3, mockDML.recordsForInsert.size()); + } + + @IsTest + private static void testRegisterNew_Bulk_ThrowExceptionOnMixedSObjectTypes() + { + List records = new List + { + new Opportunity(Name = 'A'), + new Opportunity(Name = 'B'), + new Product2(Name = 'A') + }; + + Boolean exceptionThrown = false; + try + { + Test.startTest(); + fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(MY_SOBJECTS); + uow.registerNew(records); + uow.commitWork(); + Test.stopTest(); + } + catch (fflib_SObjectUnitOfWork.UnitOfWorkException e) + { + exceptionThrown = true; + System.assert(e.getMessage().contains(Label.fflib_SObjectUnitOfWork_require_same_SObjectType)); + } + System.assert(exceptionThrown, 'Expected an exception but did not occur'); + } + @IsTest private static void testRegisterDirty_ThrowExceptionOnNewRecord() { @@ -111,7 +157,7 @@ private with sharing class fflib_SObjectUnitOfWorkTest { exceptionThrown = true; System.assertEquals( - 'New records cannot be registered as dirty', + Label.fflib_SObjectUnitOfWork_new_records_not_supported_for_update, e.getMessage(), 'Incorrect exception message thrown' ); @@ -212,15 +258,21 @@ private with sharing class fflib_SObjectUnitOfWorkTest { Boolean exceptionThrown = false; fflib_SObjectUnitOfWork unitOfWork = new fflib_SObjectUnitOfWork(MY_SOBJECTS); + final String customObjectName = 'CustomObject__c'; try { - unitOfWork.assertForEventSObjectType('CustomObject__c'); + unitOfWork.assertForEventSObjectType(customObjectName); } catch (Exception e) { exceptionThrown = true; - System.assert( - e.getMessage().contains('invalid for publishing'), + final String errorMessage = + String.format( + Label.fflib_SObjectUnitOfWork_non_event_SObjectType_used_with_publish, + new List {customObjectName}); + System.assertEquals( + errorMessage, + e.getMessage(), 'Incorrect exception message thrown' ); } @@ -240,8 +292,13 @@ private with sharing class fflib_SObjectUnitOfWorkTest catch (Exception e) { exceptionThrown = true; - System.assert( - e.getMessage().contains('not supported by this unit of work'), + final String errorMessage = + String.format( + Label.fflib_SObjectUnitOfWork_not_supported_SObjectType, + new List {'Account'}); + System.assertEquals( + errorMessage, + e.getMessage(), 'Incorrect exception message thrown' ); } @@ -358,7 +415,7 @@ private with sharing class fflib_SObjectUnitOfWorkTest * - Correct events are fired when commitWork fails during DoWork processing * */ - @isTest + @IsTest private static void testDerivedUnitOfWork_CommitDoWorkFail() { // Insert Opportunities with UnitOfWork @@ -460,6 +517,46 @@ private with sharing class fflib_SObjectUnitOfWorkTest System.assertEquals(amountUpdate.Amount, mockDML.recordsForUpdate.get(0).get(Schema.Opportunity.Amount)); } + @IsTest + private static void testRegisterDirtyRecordsWithRelationShip() + { + final Id parentId = fflib_IDGenerator.generate(Account.SObjectType); + final Id opportunityIdA = fflib_IDGenerator.generate(Opportunity.SObjectType); + final Id opportunityIdB = fflib_IDGenerator.generate(Opportunity.SObjectType); + final Account parentRecord = new Account(Id = parentId); + + Opportunity opportunityA = new Opportunity(Id = opportunityIdA); + Opportunity opportunityB = new Opportunity(Id = opportunityIdB); + + MockDML mockDML = new MockDML(); + fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(MY_SOBJECTS, mockDML); + uow.registerDirty( + new List{ opportunityA, opportunityB }, + Schema.Opportunity.AccountId, + parentRecord); + uow.commitWork(); + + System.assertEquals(2, mockDML.recordsForUpdate.size()); + System.assert( + new fflib_MatcherDefinitions.SObjectsWith( + new List>{ + new Map + { + Opportunity.Id => opportunityIdA, + Opportunity.AccountId => parentId + }, + new Map + { + Opportunity.Id => opportunityIdB, + Opportunity.AccountId => parentId + } + } + ) + .matches(mockDML.recordsForUpdate), + 'Records not registered with the correct values' + ); + } + /** * Try registering a single field as dirty on multiple records. * diff --git a/sfdx-source/apex-common/test/classes/mocks/fflib_SObjectMocks.cls b/sfdx-source/apex-common/test/classes/mocks/fflib_SObjectMocks.cls index b9e13ab12cb..ee6fa18b99a 100644 --- a/sfdx-source/apex-common/test/classes/mocks/fflib_SObjectMocks.cls +++ b/sfdx-source/apex-common/test/classes/mocks/fflib_SObjectMocks.cls @@ -102,11 +102,21 @@ public class fflib_SObjectMocks mocks.mockVoidMethod(this, 'registerNew', new List {SObject.class, Schema.sObjectField.class, SObject.class}, new List {record, relatedToParentField, relatedToParentRecord}); } + public void registerNew(List records, SObjectField relatedToParentField, SObject relatedToParentRecord) + { + mocks.mockVoidMethod(this, 'registerNew', new List {SObject.class, Schema.sObjectField.class, SObject.class}, new List {records, relatedToParentField, relatedToParentRecord}); + } + public void registerRelationship(SObject record, Schema.sObjectField relatedToField, SObject relatedTo) { mocks.mockVoidMethod(this, 'registerRelationship', new List {SObject.class, Schema.sObjectField.class, SObject.class}, new List {record, relatedToField, relatedTo}); } + public void registerRelationship(List records, SObjectField relatedToField, SObject relatedTo) + { + mocks.mockVoidMethod(this, 'registerRelationship', new List {SObject.class, Schema.sObjectField.class, SObject.class}, new List {records, relatedToField, relatedTo}); + } + public void registerRelationship(Messaging.SingleEmailMessage email, SObject relatedTo) { mocks.mockVoidMethod(this, 'registerRelationship', new List {Messaging.SingleEmailMessage.class, SObject.class}, new List {email, relatedTo});