function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Jennifer PratherJennifer Prather 

Rollup Summary Field Class and Trigger

I am trying to test a couple of triggers and classes. I am unable to get the test coverage up to 75%. Here is my classes and triggers: 
 
public class RollUpOrder {
	
    public static void upOrder(List<Order> listOfOrder){
        Set<id> accountIds = new Set<id>();
        List<Account> listOfAccounts = new List<Account>();
        List<Order> listOrders = new List<Order>();
        
        For(Order orders : listOrders){
            accountIds.add(orders.Id);
        }
        
        List<Account> listAccount = [SELECT Id, Name, Number_of_Orders__c, (SELECT Id FROM Orders) FROM Account WHERE Id IN :accountIds];
        
        For(Account acc : listAccount){
            if(acc.Orders.size()>0)
                acc.Number_of_Orders__c = acc.Orders.size();
            	listOfAccounts.add(acc);
        }
        if(listOfAccounts.size()>0)
            update listOfAccounts;
    }
}
 
public class orderCounter {
	
    public static void countOrders(Set<Id> accIds){
        List<Account> accountsToUpdate = new List<Account>();
        Map<Id, Account> accountsMap = new Map<Id, Account>([SELECT Id, Number_of_Orders__c FROM Account WHERE Id IN :accIds]);
        List<AggregateResult> res = [SELECT AccountId, count(Id) FROM Order WHERE AccountId IN :accIds GROUP BY AccountId];
        
        for(AggregateResult accRes : res){
            accountsMap.get(String.valueOf(accRes.get('AccountId'))).Number_of_Orders__c = Integer.valueOf(accRes.get('expr0'));
            accountsToUpdate.add(accountsMap.get(String.valueOf(accRes.get('AccountId'))));
        }
        update accountsToUpdate;
    }
}
 
trigger RollUpOrderTrigger on Order (after insert, after update, after delete, after undelete) {
	
    if(trigger.isAfter && (trigger.isInsert || trigger.isUpdate || trigger.isUndelete)){
        RollUpOrder.upOrder(trigger.new);
    }
    else if(trigger.isAfter && trigger.isDelete){
        RollUpOrder.upOrder(trigger.old);
    }
}
 
trigger orderCounterTrigger on Order (after insert, after update, after delete) {
	
    Set<Id> orderIds = new Set<Id>();
    
    for(Order orders : Trigger.new){
        orderIds.add(orders.AccountId);
    }        
    if(Trigger.isUpdate || Trigger.isDelete){
        for(Order ords : Trigger.old){
            orderIds.add(ords.AccountId);
        }
    }
    orderCounter.countOrders(orderIds);
}

Here is my test class:
@isTest
private class TestRollUp {
	
    @isTest static void TestOrdRollUp(){
        
        Account newAcc = new Account(Name = 'Test');
        insert newAcc;
        
        List<Order> newOrder = new List<Order>();
        Order newOrd = new Order(Name = 'Test', AccountId = newAcc.Id, EffectiveDate = Date.newInstance(2020,2,3), Status = 'Draft');
        newOrder.add(newOrd);
        insert newOrder;
        
        Test.startTest();
        newAcc.Number_of_Orders__c = newOrder.size();
        update newAcc;
        Test.stopTest();
        
        newAcc = [SELECT Number_of_Orders__c FROM Account WHERE Id = :newAcc.Id];
        System.debug('Number after trigger: ' + newAcc.Number_of_Orders__c);
        System.AssertEquals(1, newAcc.Number_of_Orders__c);
    }      
  }

I am not sure how to get the test coverage up. I am having trouble finding a way to test the IF statements.
    
Christan G 4Christan G 4
Hi Jennifer, I hope you are well. One suggestion I have to increase your testing coverage is by making a test method for each of the methods in your are testing.

For example, make a test method called upOrderTest() to test only the code that you've written for your upOrder method. Make a separate test method for orderCounter method. This may seem tedious but this will ensure that your entire code is working as expected. I also suggest bulkifying your code with multiple records. Triggers, if I am not mistaken, can hold up to 200 records at a time before processing the next batch of 200 records if more exist. Thus, in your test code, there should be at least 200 records being inserted and processed.

Also, in your test class, I noticed that you only mention updating records and not not deleting some records. Some of your test triggers are based on if records are deleted so I think you should include some in your test class. I believe this will help test out the rest of your code.

I hope my suggestions were helpful!
Christan G 4Christan G 4
One more suggestion that I forgot to mention is instead of having two triggers on the Order object, you should condense them into one trigger and just invoke both the countOrders() and upOrder() methods in the same trigger. I also suggest condensing both your RollUpOrder and orderCounter class into just one Apex class. You can even name the Apex class: OrderTriggerHelper and have the Apex Trigger be called OrderTrigger. This will help manage your code a lot better if more functionality needs to be added in the future. This is also recommended as one of Salesforce's best practices.
Maharajan CMaharajan C
Hi Jennifer,

I don't know why you have wriiten two triggers for the same purpose. I assume you are trying the different approaches for Counting the Childs in parent via Inner soql and Aggregate functions.

But I have found some issues also in your code.

Issue 1 : Apex Class :

public class RollUpOrder {
    
    public static void upOrder(List<Order> listOfOrder){
        Set<id> accountIds = new Set<id>();
        List<Account> listOfAccounts = new List<Account>();
        List<Order> listOrders = new List<Order>();
        
        For(Order orders : listOfOrder){                        ///      listOrders   ==> listOfOrder wrong list reffered here.
            accountIds.add(orders.accountId);             ///      Here you reffered the Order ID wrongly instead of Account Id
        }
        
        List<Account> listAccount = [SELECT Id, Name, Number_of_Orders__c, (SELECT Id FROM Orders) FROM Account WHERE Id IN :accountIds];
        
        For(Account acc : listAccount){
            if(acc.Orders.size()>0)
                acc.Number_of_Orders__c = acc.Orders.size();
                listOfAccounts.add(acc);
        }
        if(listOfAccounts.size()>0)
            update listOfAccounts;
    }
}


Issue 2 : In Apex Trigger:

trigger orderCounterTrigger on Order (after insert, after update, after delete) {
    
    Set<Id> orderIds = new Set<Id>();
    
    if(Trigger.isInsert)              ///   You need the Trigger Context variable check here otherwise you will get Exception in after delete
    {

        for(Order orders : Trigger.new){
            orderIds.add(orders.AccountId);
        }  
    }
    
    else if(Trigger.isUpdate || Trigger.isDelete){
        for(Order ords : Trigger.old){
            orderIds.add(ords.AccountId);
        }
    }
    orderCounter.countOrders(orderIds);
}


Updated Test Class:

@isTest
private class TestRollUp {
    
    static testmethod void TestOrdRollUp(){
        
        Account newAcc = new Account(Name = 'Test');
        insert newAcc;
        
        List<Order> newOrder = new List<Order>();
        Order newOrd = new Order(Name = 'Test', AccountId = newAcc.Id, EffectiveDate = Date.newInstance(2020,2,3), Status = 'Draft');
        newOrder.add(newOrd);
        insert newOrder;
    }      
    
    static testmethod void TestOrdRollUp1(){
        
        Account newAcc = new Account(Name = 'Test');
        insert newAcc;
        
        List<Order> newOrder = new List<Order>();
        Order newOrd = new Order(Name = 'Test', AccountId = newAcc.Id, EffectiveDate = Date.newInstance(2020,2,3), Status = 'Draft');
        newOrder.add(newOrd);
        insert newOrder;
        
        delete newOrder;
    }
}


Thanks,
Maharajan.C
Jennifer PratherJennifer Prather
Thank you for the information. I know that best practices are to use only one trigger. I am new to development and had found the examples online. Is there a better way to create a rollup summary for a lookup relationship? 
Christan G 4Christan G 4
Hi Jennifer and unfortunately, no. Roll Up summary fields are exclusive to objects that have a master-detail relationship between them. If you are trying to write an apex class that will count the amount of relative records that each parent record have, I can help. I can provide you a code script by tomorrow or the day after the latest. I actually have to do the same for a project that I am experimenting with in my dev org so it'll be good practice. I'll input comments as well so it is easy to follow my logic when writing the code.
Maharajan CMaharajan C
Hi Jennifer,

In salesforce lookup relatioship we don't have any other better options than Trigger to rollup the childs in parent.

We can use the Flows also for this but you are new so better go with triggers.

In trigger also we can use the multiple approaches to achieve this.

1. Using Inner SOQL.
2. Using Aggregate function.
3. Using Apex collection.

Already you have tried the first two approaches in your above question. 

And based on me you can go with the first approach because your code also look fine only small mistakes are in Apex Class. Please use the below updated one:

Apex Trigger:
 
trigger RollUpOrderTrigger on Order (after insert, after update, after delete, after undelete) {
	
    if(trigger.isAfter && (trigger.isInsert || trigger.isUpdate || trigger.isUndelete)){
        RollUpOrder.upOrder(trigger.new);
    }
    else if(trigger.isAfter && trigger.isDelete){
        RollUpOrder.upOrder(trigger.old);
    }
}

Apex Class:
 
public class RollUpOrder {
    
    public static void upOrder(List<Order> listOfOrder){
        Set<id> accountIds = new Set<id>();
        List<Account> listOfAccounts = new List<Account>();
        
        For(Order orders : listOfOrder){                       
            accountIds.add(orders.accountId);            
        }
        
        List<Account> listAccount = [SELECT Id, Name, Number_of_Orders__c, (SELECT Id FROM Orders) FROM Account WHERE Id IN :accountIds];
        
        For(Account acc : listAccount){
            if(acc.Orders.size()>0)
                acc.Number_of_Orders__c = acc.Orders.size();
                listOfAccounts.add(acc);
        }
        if(listOfAccounts.size()>0)
            update listOfAccounts;
    }
}


Thanks,
​​​​​​​Maharajan.C
Jennifer PratherJennifer Prather
Thank you for the responses. I used the suggestions; however, I still cannot figure out how to get the code coverage up. My class is still at 61%.  I underlined the code that I am not able to cover.
public class RollUpOrder {
    
    public static void upOrder(List<Order> listOfOrder){
        Set<id> accountIds = new Set<id>();
        List<Account> listOfAccounts = new List<Account>();
        
        For(Order orders : listOfOrder){                       
            accountIds.add(orders.accountId);            
        }
        
        List<Account> listAccount = [SELECT Id, Name, Number_of_Orders__c, (SELECT Id FROM Orders) FROM Account WHERE Id IN :accountIds];
        
        For(Account acc : listAccount){
            if(acc.Orders.size()>0)
                acc.Number_of_Orders__c = acc.Orders.size();
                listOfAccounts.add(acc);
        }
        if(listOfAccounts.size()>0)
            update listOfAccounts;
    }
}
Christan G 4Christan G 4
One question is the if(acc.Orders.size > 0) statement highlighted in blue but everything below it is marked as red? If yes, then that means the if statement is returning false and this ignoring the code below it. To resolve this, you may have to create some test Orders and associated it to your test account so that the size is greater than 0.
Jennifer PratherJennifer Prather
If(acc.Orders.size > 0) is not blue. It is red too. I went and commented out the orderCounter class and trigger. I thought from previous answer that they were not necessary. Now when it test it in the front end, it does not update the account record at all. 
Christan G 4Christan G 4
Are you referring to the code provided by Maharajan? I just realized also that he included only the upOrder() method and not the orderCounter() method. To fix this, you can simply copy and paste its code underneath the upOrder() method. The trigger code will also have to be modified under where it states if(Trigger.isUpdate || Trigger.isDelete) to something similar your original post under the orderCounterTrigger to get those IDs. Unfortunately, I am not able to write the code at the moment due to working on a task. If I do get time before your issue gets resolved, I'll write some for you.
Jennifer PratherJennifer Prather
I figured out how to merge the classes and triggers into one. I got the class coverage up to 76%. What I had underlined previously is still not being covered.