My primary use case involves a tree as follows, and is what the following code is based on testing (Schedule Lines are created via trigger, so no need to manually address those in my ChildRelationships Custom Setting). You'll need to adapt to your own use cases:
1. Create a new Apex Class (Setup > Develop > Apex Classes > New) and paste the following code:
@isTest(SeeAllData=true) public without sharing class UtilsTest { public static List<Account> createFieldSalesAccounts(Integer count, Id parentId) { List<Account> listOf = new List<Account>(); for (Integer i = 0; i < count; i++) { Account toInsert = new Account(); toInsert.RecordTypeId = UtilsVariables.RECORDTYPEID_ACCOUNT_FIELD_SALES; toInsert.Name = 'Test Field Sales Account ' + i; if (parentId != null) toInsert.ParentId = parentId; listOf.add(toInsert); } insert listOf; return listOf; } public static List<Opportunity> createBoards(Integer count, Id accountId) { List<Opportunity> listOf = new List<Opportunity>(); List<End_Equipment__c> endequipments = createEndEquipments(1); if (accountId == null) accountId = createFieldSalesAccounts(1, null)[0].Id; for (Integer i = 0; i < count; i++) { Opportunity toInsert = new Opportunity(); toInsert.RecordTypeId = UtilsVariables.RECORDTYPEID_OPPORTUNITY_BOARD; toInsert.Name = 'Test Board ' + i; toInsert.StageName = 'Architecture'; toInsert.CloseDate = Date.Today(); toInsert.Board_Design_Years__c = 1; toInsert.Board_Design_Months__c = 3; toInsert.Board_End_Equipment__c = endequipments[0].Id; toInsert.AccountId = accountId; listOf.add(toInsert); } insert listOf; return listOf; } public static List<Opportunity> createSockets(Integer count, Id boardId) { List<Opportunity> listOf = new List<Opportunity>(); List<Socket_Function__c> socketfunctions = createSocketFunctions(1); if (boardId == null) boardId = createBoards(1, null)[0].Id; Id accountId = [SELECT AccountId FROM Opportunity WHERE Id = :boardId].AccountId; for (Integer i = 0; i < count; i++) { Opportunity toInsert = new Opportunity(); toInsert.RecordTypeId = UtilsVariables.RECORDTYPEID_OPPORTUNITY_SOCKET; toInsert.Name = 'Test Socket ' + i; toInsert.StageName = 'Potential'; toInsert.CloseDate = Date.Today(); toInsert.AccountId = accountId; toInsert.Board_Name__c = boardId; toInsert.Socket_Function__c = socketfunctions[0].Id; listOf.add(toInsert); } insert listOf; return listOf; } public static List<Product2> createProducts(Integer count) { List<Product2> listOf = new List<Product2>(); for (Integer i = 0; i < count; i++) { Product2 toInsert = new Product2(); toInsert.Name = 'Test Product ' + i; listOf.add(toInsert); } insert listOf; return listOf; } public static List<PricebookEntry> createPriceBookEntries(Integer count) { List<PricebookEntry> listOf = new List<PricebookEntry>(); List<Product2> products = createProducts(count); List<Pricebook2> pricebooks = [SELECT Id FROM Pricebook2 WHERE IsStandard = true]; for (Product2 product : products) { PricebookEntry toInsert = new PricebookEntry(); toInsert.Product2Id = product.Id; toInsert.UnitPrice = 0; toInsert.Pricebook2Id = pricebooks[0].Id; toInsert.isActive = true; listOf.add(toInsert); } insert listOf; return listOf; } public static List<OpportunityLineItem> createOpportunityLineItems(List<Opportunity> opps, List<PricebookEntry> pricebookentries) { List<OpportunityLineItem> listOf = new List<OpportunityLineItem>(); for (Opportunity opp : opps) { Boolean isFirst = true; for (PricebookEntry pricebookentry : pricebookentries) { OpportunityLineItem toInsert = new OpportunityLineItem(); toInsert.PricebookEntryId = pricebookentry.Id; toInsert.OpportunityId = opp.Id; toInsert.Quantity = 1; if (opp.RecordTypeId == UtilsVariables.RECORDTYPEID_OPPORTUNITY_SOCKET) toInsert.UnitPrice = 1.5; if (opp.RecordTypeId == UtilsVariables.RECORDTYPEID_OPPORTUNITY_SALES_PLAN_PRODUCT) toInsert.UnitPrice = 0; if (opp.RecordTypeId == UtilsVariables.RECORDTYPEID_OPPORTUNITY_SALES_PLAN_PRODUCT) toInsert.Annual_Value__c = 12; if (opp.RecordTypeId == UtilsVariables.RECORDTYPEID_OPPORTUNITY_SOCKET && isFirst) toInsert.Primary__c = true; listOf.add(toInsert); isFirst = false; } } insert listOf; return listOf; } public static List<Socket_Function__c> createSocketFunctions(Integer count) { List<Socket_Function__c> listOf = new List<Socket_Function__c>(); for (Integer i = 0; i < count; i++) { Socket_Function__c toInsert = new Socket_Function__c(); toInsert.Name = 'Test Function ' + i; listOf.add(toInsert); } insert listOf; return listOf; } public static List<End_Equipment__c> createEndEquipments(Integer count) { List<End_Equipment__c> listOf = new List<End_Equipment__c>(); for (Integer i = 0; i < count; i++) { End_Equipment__c toInsert = new End_Equipment__c(); toInsert.Name = 'Test End Equipment ' + i; listOf.add(toInsert); } insert listOf; return listOf; } public static List<Calendar_Period__c> getPeriods(Integer count, String type) { List<Calendar_Period__c> listOf = [SELECT Id, Name, Start_Date__c, End_Date__c FROM Calendar_Period__c WHERE RecordTypeId = :type AND Start_Date__c >= :Date.today() AND Start_Date__c <= :Date.today().addMonths(count * 12)]; return listOf; } public static List<Schedule__c> createSchedules(Integer count, Id boardId) { List<Schedule__c> listOf = new List<Schedule__c>(); List<Calendar_Period__c> periods = getPeriods(count, UtilsVariables.RECORDTYPEID_CALENDAR_PERIOD_FISCAL_YEAR); for (Calendar_Period__c period : periods) { Schedule__c toInsert = new Schedule__c(); toInsert.Fiscal_Year__c = period.Id; toInsert.Volume__c = 10; if (boardId != null) toInsert.Opportunity_Name__c = boardId; listOf.add(toInsert); } insert listOf; return listOf; } }
12. Create a new Apex Class (Setup > Develop > Apex Classes > New) and paste the following code:
@isTest(SeeAllData=true) private class UtilsDeeperClone_test { private static Integer expectedProducts = 3; private static Integer expectedBoards = 1; private static Integer expectedSockets = 1; private static Integer expectedOpportunityLineItems = expectedSockets * expectedProducts; private static Integer expectedSchedules = 4; private static Integer expectedScheduleLines = expectedSockets * expectedSchedules; static testMethod void testCloneWithoutChatterBoard() { Boolean withChatter = false; List<PricebookEntry> pricebookentries = UtilsTest.createPricebookEntries(expectedProducts); List<Opportunity> boards = UtilsTest.createBoards(expectedBoards, null); List<Opportunity> sockets = UtilsTest.createSockets(expectedSockets, boards[0].Id); List<OpportunityLineItem> opportunitylineitems = UtilsTest.createOpportunityLineItems(sockets, pricebookentries); List<Schedule__c> schedules = UtilsTest.createSchedules(expectedSchedules, boards[0].Id); System.assertEquals(expectedBoards, boards.size(), 'expected ' + expectedBoards + ' boards'); System.assertEquals(expectedSockets, sockets.size(), 'expected ' + expectedSockets + ' sockets'); System.assertEquals(expectedSchedules, schedules.size(), 'expected ' + expectedSchedules + ' schedules'); List<Schedule_Line__c> schedulelines = [SELECT Id FROM Schedule_Line__c WHERE Schedule__c IN :schedules]; System.assertEquals(expectedScheduleLines, schedulelines.size(), 'expected ' + expectedScheduleLines + ' schedule lines'); Test.startTest(); Id clonedId = UtilsDeeperClone.clone(boards[0].Id, withChatter); Test.stopTest(); List<FeedItem> chatterFeeds = [SELECT Id FROM FeedItem WHERE ParentId = :clonedId]; List<Opportunity> clonedBoards = [SELECT Id, RecordType.Name FROM Opportunity WHERE Id = :clonedId]; List<Opportunity> clonedSockets = [SELECT Id FROM Opportunity WHERE Board_Name__c = :clonedId]; List<OpportunityLineItem> clonedParts = [SELECT Id FROM OpportunityLineItem WHERE OpportunityId IN :clonedSockets]; List<Schedule__c> clonedSchedules = [SELECT Id FROM Schedule__c WHERE Opportunity_Name__c = :clonedId]; List<Schedule_Line__c> clonedScheduleLines = [SELECT Id FROM Schedule_Line__c WHERE Schedule__c IN :schedules]; System.assertEquals(withChatter, !chatterFeeds.isEmpty(), 'expected chatter? ' + withChatter + ' ' + chatterFeeds.size()); System.assertEquals(expectedBoards, clonedBoards.size(), 'expected ' + expectedBoards + ' cloned boards'); System.assertEquals(expectedSockets, clonedSockets.size(), 'expected ' + expectedSockets + ' cloned sockets'); System.assertEquals(expectedOpportunityLineItems, clonedParts.size(), 'expected ' + expectedOpportunityLineItems + ' cloned parts'); System.assertEquals(expectedSchedules, clonedSchedules.size(), 'expected ' + expectedSchedules + ' cloned schedules'); System.assertEquals(expectedScheduleLines, clonedScheduleLines.size(), 'expected ' + expectedScheduleLines + ' cloned schedule lines'); } static testMethod void testCloneWithChatterBoard() { Boolean withChatter = true; List<PricebookEntry> pricebookentries = UtilsTest.createPricebookEntries(expectedProducts); List<Opportunity> boards = UtilsTest.createBoards(expectedBoards, null); List<Opportunity> sockets = UtilsTest.createSockets(expectedSockets, boards[0].Id); List<OpportunityLineItem> opportunitylineitems = UtilsTest.createOpportunityLineItems(sockets, pricebookentries); List<Schedule__c> schedules = UtilsTest.createSchedules(expectedSchedules, boards[0].Id); System.assertEquals(expectedBoards, boards.size(), 'expected ' + expectedBoards + ' boards'); System.assertEquals(expectedSockets, sockets.size(), 'expected ' + expectedSockets + ' sockets'); System.assertEquals(expectedSchedules, schedules.size(), 'expected ' + expectedSchedules + ' schedules'); List<Schedule_Line__c> schedulelines = [SELECT Id FROM Schedule_Line__c WHERE Schedule__c IN :schedules]; System.assertEquals(expectedScheduleLines, schedulelines.size(), 'expected ' + expectedScheduleLines + ' schedule lines'); Test.startTest(); Id clonedId = UtilsDeeperClone.clone(boards[0].Id, withChatter); Test.stopTest(); List<FeedItem> chatterFeeds = [SELECT Id FROM FeedItem WHERE ParentId = :clonedId]; List<Opportunity> clonedBoards = [SELECT Id, RecordType.Name FROM Opportunity WHERE Id = :clonedId]; List<Opportunity> clonedSockets = [SELECT Id FROM Opportunity WHERE Board_Name__c = :clonedId]; List<OpportunityLineItem> clonedParts = [SELECT Id FROM OpportunityLineItem WHERE OpportunityId IN :clonedSockets]; List<Schedule__c> clonedSchedules = [SELECT Id FROM Schedule__c WHERE Opportunity_Name__c = :clonedId]; List<Schedule_Line__c> clonedScheduleLines = [SELECT Id FROM Schedule_Line__c WHERE Schedule__c IN :schedules]; System.assertEquals(withChatter, !chatterFeeds.isEmpty(), 'expected chatter? ' + withChatter + ' ' + chatterFeeds.size()); System.assertEquals(expectedBoards, clonedBoards.size(), 'expected ' + expectedBoards + ' cloned boards'); System.assertEquals(expectedSockets, clonedSockets.size(), 'expected ' + expectedSockets + ' cloned sockets'); System.assertEquals(expectedOpportunityLineItems, clonedParts.size(), 'expected ' + expectedOpportunityLineItems + ' cloned parts'); System.assertEquals(expectedSchedules, clonedSchedules.size(), 'expected ' + expectedSchedules + ' cloned schedules'); System.assertEquals(expectedScheduleLines, clonedScheduleLines.size(), 'expected ' + expectedScheduleLines + ' cloned schedule lines'); } static testMethod void testCloneWithoutChatterSocket() { Boolean withChatter = false; List<PricebookEntry> pricebookentries = UtilsTest.createPricebookEntries(expectedProducts); List<Opportunity> sockets = UtilsTest.createSockets(expectedSockets, null); List<OpportunityLineItem> opportunitylineitems = UtilsTest.createOpportunityLineItems(sockets, pricebookentries); System.assertEquals(expectedSockets, sockets.size(), 'expected ' + expectedSockets + ' sockets'); Test.startTest(); Id clonedId = UtilsDeeperClone.clone(sockets[0].Id, withChatter); Test.stopTest(); List<FeedItem> chatterFeeds = [SELECT Id FROM FeedItem WHERE ParentId = :clonedId]; List<Opportunity> clonedSockets = [SELECT Id FROM Opportunity WHERE Id = :clonedId]; List<OpportunityLineItem> clonedParts = [SELECT Id FROM OpportunityLineItem WHERE OpportunityId = :clonedId]; System.assertEquals(withChatter, !chatterFeeds.isEmpty(), 'expected chatter? ' + withChatter + ' ' + chatterFeeds.size()); System.assertEquals(expectedSockets, clonedSockets.size(), 'expected ' + expectedSockets + ' cloned sockets'); System.assertEquals(expectedOpportunityLineItems, clonedParts.size(), 'expected ' + expectedOpportunityLineItems + ' cloned parts'); } static testMethod void testCloneWithChatterSocket() { Boolean withChatter = true; List<PricebookEntry> pricebookentries = UtilsTest.createPricebookEntries(expectedProducts); List<Opportunity> sockets = UtilsTest.createSockets(expectedSockets, null); List<OpportunityLineItem> opportunitylineitems = UtilsTest.createOpportunityLineItems(sockets, pricebookentries); System.assertEquals(expectedSockets, sockets.size(), 'expected ' + expectedSockets + ' sockets'); Test.startTest(); Id clonedId = UtilsDeeperClone.clone(sockets[0].Id, withChatter); Test.stopTest(); List<FeedItem> chatterFeeds = [SELECT Id FROM FeedItem WHERE ParentId = :clonedId]; List<Opportunity> clonedSockets = [SELECT Id FROM Opportunity WHERE Id = :clonedId]; List<OpportunityLineItem> clonedParts = [SELECT Id FROM OpportunityLineItem WHERE OpportunityId = :clonedId]; System.assertEquals(withChatter, !chatterFeeds.isEmpty(), 'expected chatter? ' + withChatter + ' ' + chatterFeeds.size()); System.assertEquals(expectedSockets, clonedSockets.size(), 'expected ' + expectedSockets + ' cloned sockets'); System.assertEquals(expectedOpportunityLineItems, clonedParts.size(), 'expected ' + expectedOpportunityLineItems + ' cloned parts'); } }
A few notes:
- You will need to modify the creatObject() methods in the UtilsTest class to abide by your org's requirements (required fields, values, etc)
- This test code covers 98% in my org
Happy Coding!