0

I created an apex class that handles field computation in Event object. This logic is called on a trigger that fires every after update of ObjectA__c. ObjectA__c contains all the formula fields, mostly roll-up summary fields and other formula fields, too. Now, Event and ObjectA__c don't have a direct relationship, so there's a need to query on multiple objects just to compute the Computed_Value__c field in Event detail page.

This requirement works perfectly as expected.

But problem occurs in test class, as it returns an error when when it's being run:

System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, MyTrigger: execution of AfterUpdate

caused by: System.MathException: Divide by 0

Error pertains to this line: insert childList;

Here's what I have right now:

Apex Class:

public class MyClass {

        public static void computeMethod(List<ObjectA__c> aList) {

            Set<Id> aId = new Set<Id>();
            Set<Id> cId = new Set<Id>();
            Decimal recType1formula1 = 0;
            Decimal recType1formula2 = 0;
            Decimal recType2formula1 = 0;
            Decimal recType2formula2 = 0;    
            Decimal numerator = 0;
            Decimal denominator = 0;

            for(ObjectA__c a : aList) {
                  aId.add(a.Id); 
            }    

            List<ObjectA__c> aList = [SELECT Id, Name, RecordType1_Formula1__c, RecordType1_Formula2__c, RecordType2_Formula1__c, RecordType2_Formula2__c,(SELECT Id, Name, ObjectC__c FROM ObjectB__r) FROM ObjectA__c WHERE
                                      Id IN: aId];

            for(ObjectB__c b : aList[0].ObjectB__r) {
                cId.add(b.ObjectC__c);
            }    

            List<ObjectB__c> bList = [SELECT Id, Name, ObjectC__c, ObjectA__c, ObjectA__r.Name, ObjectA__r.RecordType.Name, ObjectA__r.Checkbox__c,
                                      ObjectA__r.RecordType1_Formula1__c, ObjectA__r.RecordType1_Formula2__c, ObjectA__r.RecordType2_Formula1__c, ObjectA__r.RecordType2_Formula2__c 
                                      FROM ObjectB__c WHERE ObjectC__c IN: cId]; 

            List<Event> eventList = [SELECT Id, ObjectC__c, Computed_Value__c FROM Event WHERE ObjectC__c IN: cId];             

            for(ObjectB__c b : bList) {

                if(b.ObjectA__r.RecordType.Name == 'Type A') {
                    if(b.ObjectA__r.Checkbox__c) {
                        recType1formula1 = 0;
                        recType1formula2 = 0;
                    }
                    else {
                        recType1formula1 += b.ObjectA__r.RecordType1_Formula1__c;
                        recType1formula2 += b.ObjectA__r.RecordType1_Formula2__c; 
                    }

                }
                if(b.ObjectA__r.RecordType.Name == 'Type B') {
                    if(b.ObjectA__r.Checkbox__c) {
                        recType2formula1 = 0;
                        recType2formula2 = 0;
                    }
                    else {
                        recType2formula1 += b.ObjectA__r.RecordType2_Formula1__c;
                        recType2formula2 += b.ObjectA__r.RecordType2_Formula2__c;
                    }

                }       
            }

            numerator = recType1formula1 + recType2formula1;      
            denominator = recType1formula2 + recType2formula2;    

            for(Event evnt : eventList) {
                evnt.Computed_Value__c = numerator/denominator;          
            }

            update eventList;

        } 
}

Apex Trigger:

trigger MyTrigger on ObjectA__c(after update) {
    MyClass.computeMethod(Trigger.New);
}

Test Class:

@isTest
public class MyClassTest{

     public static testMethod void MyClassTest(){

        Set<Id> aIds = new Set<Id>();

        Event evt = new Event();
        evt.Subject = 'The Subject';
        evt.StartDateTime = dateTime.now(); 
        testEvent.EndDateTime = dateTime.now();
        insert evt;

        Account acc = new Account();
        acc.Name = 'The Account'; 
        insert acc;         

        ObjectC__c c = new ObjectC__c();
        c.Event_Link__c = evt.Id;
        c.Account__c = acc.Id;
        insert c;

        evt.ObjectC__c = c.Id;
        update evt;

        List<ObjectA__c> aList = new List<ObjectA__c>();
        ObjectA__c a1 = new ObjectA__c();    
        a1.Name = 'A1';
        a1.RecordTypeId = Schema.SObjectType.ObjectA__c.getRecordTypeInfosByName().get('Type A').getRecordTypeId();

        ObjectA__c a2 = new ObjectA__c();
        a2.Name = 'A2';
        a2.RecordTypeId = Schema.SObjectType.ObjectA__c.getRecordTypeInfosByName().get('Type B').getRecordTypeId();

        aList.add(a1);
        aList.add(a2);

        insert aList;

        List<ObjectB__c> bList = new List<ObjectB__c>();
        ObjectB__c b1 = new ObjectB__c(); 
        b1.ObjectC__c = c.Id;
        b1.Field1__c = 'sample1';
        b1.ObjectA__c = a1.Id;

        ObjectB__c b2 = new ObjectB__c(); 
        b2.ObjectC__c = c.Id;
        b2.Field1__c = 'sample2';
        b2.ObjectA__c = a2.Id;

        bList.add(b1);
        bList.add(b12);

        insert bList;

        for(ObjectA__c a : aList) {
            aIds.add(a.Id);    
        }

        List<Child_Object__c> childList = new List<Child_Object__c>();
        Child_Object__c co1 = new Child_Object__c();
        co1.ObjectA__c = a1.Id;
        co1.RecordTypeId = Schema.SObjectType.Child_Object__c.getRecordTypeInfosByName().get('Type A').getRecordTypeId();
        co1.Num__c = 100;

        Child_Object__c co2 = new Child_Object__c();
        co2.ObjectA__c = a2.Id;
        co2.RecordTypeId = Schema.SObjectType.Child_Object__c.getRecordTypeInfosByName().get('Type B').getRecordTypeId();
        co2.Num__c = 100;

        childList.add(co1); 
        childList.add(co2);

        insert childList;

        List<ObjectA__c> aList2 = [SELECT Id, Name, RecordType1_Formula1__c, RecordType1_Formula2__c, RecordType2_Formula1__c, RecordType2_Formula2__c, 
                                   Checkbox__c FROM ObjectA__c WHERE Id IN: aList];  

        update aList2;

        for(ObjectA__c aloop : aList2) {
            System.assertequals(100, aloop.RecordType1_Formula1__c);
            System.assertequals(100, aloop.RecordType1_Formula2__c);
            System.assertequals(100, aloop.RecordType2_Formula1__c);
            System.assertequals(100, aloop.RecordType2_Formula2__c);
        }


        List<ObjectB__c> bList2 = [SELECT Id, Name, ObjectC__c, ObjectA__c, ObjectA__r.Name, ObjectA__r.RecordType.Name,   
                                   ObjectA__r.RecordType1_Formula1__c, ObjectA__r.RecordType1_Formula2__c, ObjectA__r.RecordType2_Formula1__c,
                                   ObjectA__r.RecordType2_Formula2__c FROM ObjectB__c WHERE Id IN: bList AND ObjectC__c =: c.Id]; 

        for(ObjectB__c bloop : bList2) {
            System.assertequals(100, bloop.ObjectA__r.RecordType1_Formula1__c);
            System.assertequals(100, bloop.ObjectA__r.RecordType1_Formula2__c);
            System.assertequals(100, bloop.ObjectA__r.RecordType2_Formula1__c);
            System.assertequals(100, bloop.ObjectA__r.RecordType2_Formula2__c);
        }

        evt = [SELECT Id, Computed_Value__c FROM Event WHERE ObjectC__c =: c.Id];

        System.assertequals(100, testEvent.Computed_Value__c);

        MyClass mc = new MyClass();
     }
}
5
  • 2
    Verifying that your code works as you expect it to work is the most important part of unit testing. It’s also one of the things that Force.com developers commonly neglect. Unit tests that do not verify the results of the code aren’t true unit tests. They are commonly referred to as smoke tests, which aren’t nearly as effective or informative as true unit tests. - How to Write Good Unit Tests
    – Adrian Larson
    Mar 15, 2018 at 14:11
  • @AdrianLarson I've modified my code and used verifications. I've encountered another issue now, so I've modified my post.
    – Gaynerys
    Mar 16, 2018 at 5:35
  • You have some code that shouldn't even compile. It's hard to give good feedback when you reference undefined variables like sa.
    – Adrian Larson
    Mar 16, 2018 at 16:11
  • Also you don't have to compare checkbox fields to true. You can just do if (record.Checkbox__c) { ... }.
    – Adrian Larson
    Mar 16, 2018 at 16:29
  • Apologies with the undefined variable. I've modified the code in my post just now.
    – Gaynerys
    Mar 16, 2018 at 18:47

1 Answer 1

1

This method is a great example of how greater Separation Of Concerns can help you write more testable code. You're trying to break off more than you can chew in one bite here. And if you need to use dependency injection to work around this issue, you're going to have a hard time working it into your code.

In this particular case, you have a lot of stateful information flying around, so you will probably want to add an inner class to help you break logic into smaller discrete chunks. You may also want to look at using the fluent pattern to make calling it less verbose.

Hopefully this recommendation makes sense at a generic level, and you can take the concept to completion.

public with sharing class MyCalculator
{
    final List<ObjectA__c> records;
    List<ObjectC__c> relatedRecords;
    public MyCalculator(List<ObjectA__c> records)
    {
        this.records = [
            SELECT ... FROM ObjectA__c WHERE Id IN :records
        ];
    }
    public MyCalculator queryObjectC()
    {
        Set<Id> cIds = new Set<Id>();
        // populate
        relatedRecords = [
            SELECT ... FROM ObjectC___c WHERE Id IN :cIds
        ];
        return this;
    }
    public void computeEventValues()
    { // end chaining
        for (ObjectC__c relatedRecord : relatedRecords)
        {
            // do stuff
        }
    }
}

So already this approach allows you to more granularly verify you are getting back the correct ObjectC__c records. But where it really shines is now you could plug in some dependency injection to simplify testing:

public virtual with sharing class MyCalculator
{
    // same implementation as above, but add in below
    Decimal getNumerator()
    {
        return getRecType1formula1() + getRecType2formula1;   
    }
    Decimal getDenominator()
    {
        return getRecType1formula2() + getRecType2formula2();
    }
    protected virtual Decimal getRecType1Formula1() { /*implementation*/ }
    protected virtual Decimal getRecType1Formula2() { /*implementation*/ }
    protected virtual Decimal getRecType2Formula1() { /*implementation*/ }
    protected virtual Decimal getRecType2Formula2() { /*implementation*/ }
}

Now in your unit test, you can use dependency injection!

@IsTest
class MyCalculatorTests
{
    class InjectedCalculator extends MyCalculator
    {
        Decimal r1f1, r1f2, r2f1, r2f2;
        public Decimal getRecordType1Formula1() { return r1f1; }
        public Decimal getRecordType1Formula2() { return r1f2; }
        public Decimal getRecordType2Formula1() { return r2f1; }
        public Decimal getRecordType2Formula2() { return r2f2; }
    }
}

Now you can test how your methods consume these data points independently of their retrieval. And if you want to, you can more specifically pinpoint where you are having trouble with that retrieval by making these virtual methods @TestVisible or even public.

Slight digression, I tend to go for the most restrictive visibility that will work for my application, but avoid @TestVisible wherever possible. That said, if a value is immutable, or a method call has no side-effects, I see little risk in just making it public (never use global unless you actually need it and know why).

1
  • Where does InjectedCalculator from your test class come from?
    – Gaynerys
    Mar 20, 2018 at 14:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .