[SalesForce] Before Update trigger is not working with Before Insert

I have a trigger with two events, Before Insert and Before Update. They are working fine when I make an object or update an object in org, but when I'm trying to create a object and update that object in testclass then only insert is working update is not working. If I remove before insert event from trigger then before update is working.
[before insert + before update = before insert working but before update not working],
individually ,
[before insert = working ]
[before update = working ]

I am using a trigger template,
Here is the code :

Trigger :

trigger CompTrigger on Comp__c (before insert , before update)
{
    new TriggerTemplateV2.TriggerManager(new TriggerHandler()).runHandlers();
}

Handler :

public with sharing class TriggerHandler extends TriggerTemplateV2.TriggerHandlerAdapter {

    // Will be called before insertion of object    
    public override void onBeforeInsert (List<sObject> newList)
    {
        system.debug('IN onBeforeInsert');
    }

    // Will be called before updation of object 
    public override void onBeforeUpdate (List<sObject> newList, Map<Id, sObject> newMap, List<sObject> oldList, Map<Id, sObject> oldMap)
    {
        system.debug('IN onBeforeUpdate');
    }

}


        /**
         * Helper method to get the class name for a given Object
         * Used as the key identifier of the trigger handler classes
         */
        private String getClassName(Object o)
        {
            String className = null;
            if(o != null)
            {
                // The value looks something like this: "ProjectTriggerHandler:[idToOldProjectMap=null, newProjectList=null]"
                String objectAsString = String.valueOf(o);
                className = objectAsString.substring(0,objectAsString.indexOf(':'));
            }
            return className;
        }
    }

    /**
     * Interface definitions for the before and after trigger handlers
     *
     */

    public interface BeforeTriggerHandler
    {
        void onBeforeInsert (List<sObject> newList);

        void onBeforeUpdate (List<sObject> newList, Map<Id, sObject> newMap, List<sObject> oldList, Map<Id, sObject> oldMap);

        void onBeforeDelete (List<sObject> oldList, Map<Id, sObject> oldMap);
    }

    public interface AfterTriggerHandler
    {
        void onAfterInsert (List<sObject> newList, Map<Id, sObject> newMap);

        void onAfterUpdate (List<sObject> newList, Map<Id, sObject> newMap, List<sObject> oldList, Map<Id, sObject> oldMap);

        void onAfterDelete (List<sObject> oldList, Map<Id, sObject> oldMap);

        void onAfterUndelete (List<sObject> newList, Map<Id, sObject> newMap);
    }

    /**
     * An abstract adapter class that can be extended for convenience. By extending
     * this class only the relevant handler methods need to be implemented.
     */

    public abstract class TriggerHandlerAdapter implements BeforeTriggerHandler, AfterTriggerHandler
    {
        public virtual void onBeforeInsert (List<sObject> newList) {}

        public virtual void onBeforeUpdate (List<sObject> newList, Map<Id, sObject> newMap, List<sObject> oldList, Map<Id, sObject> oldMap) {}

        public virtual void onBeforeDelete (List<sObject> oldList, Map<Id, sObject> oldMap) {}

        public virtual void onAfterInsert (List<sObject> newList, Map<Id, sObject> newMap) {}

        public virtual void onAfterUpdate (List<sObject> newList, Map<Id, sObject> newMap, List<sObject> oldList, Map<Id, sObject> oldMap) {}

        public virtual void onAfterDelete (List<sObject> oldList, Map<Id, sObject> oldMap) {}

        public virtual void onAfterUndelete (List<sObject> newList, Map<Id, sObject> newMap) {}
    }
}

TestClass :

@isTest
public class Test_TriggerHandler {

    static testmethod void TriggerHandler_Test()
    {   
        system.debug('in test method'); 
        Test.startTest();

        Comp__c comp = new Comp__c();
        insert comp;

        cmp.Status__c = 'failed' ;
        update comp;

        Test.stopTest();
    }
}

Best Answer

This appears to be a flaw in your trigger framework's design.

Your controlling variables, enabledTriggerEventName, isInitialized, enabledBeforeHandlerClassName, and enabledAfterHandlerClassName are all static.

The first time you issue a DML call in your test class, isInitialized is false, and you go through some extra setup in your constructor to set enabledBeforeHandlerClassName, enabledAfterHandlerClassName, and enabledTriggerEventName.

The enabledbefore/after handler class names (the static ones) aren't really a concern in this particular instance, but I suspect it'll cause you issues once you have more than one SObject's trigger using your framework.

The issue in this particular case lies with enabledTriggerEventName.

Still on your first DML call in your test class, the insert call, enabledTriggerEventName is set to insert. Your isInitialized static variable is set to true, and the constructor call finishes.

You then move on to the runHandlers() call.

  • allTriggersDisabled is false, so you pass the first if statement.
  • Trigger.isBefore is true, beforeHandler is not null, and enabledBeforeHandlerClassName is equal to beforeHandler, so you pass the second if statement
  • Trigger.isInsert is true, and enabledTriggerEventName == 'insert', so you pass the the third if statement

At this point, your actual handler code is called, and you get your debug statement printed for the insert event. Your insert dml in your test method completes.

We now move on to your update DML call.

isInitialized, being static, is still true since your insert and update calls are in the same transaction. Thus, enabledBeforeHandlerClassName, enabledAfterHandlerClassName, your invocation lists, and enabledTriggerEventName are not updated.

Your constructor completes, and we move to calling runHandlers().

  • allTriggersDisabled is false, so you pass the first if statement.
  • Trigger.isBefore is true, beforeHandler is not null, and enabledBeforeHandlerClassName is equal to beforeHandler, so you pass the second if statement (N.B. this is where you'll run into issues when you have two different triggers using your framework that run in the same transaction)
  • Trigger.isInsert is false, so you fail the third if statement, and move on to the first else if
  • Trigger.isUpdate is true, but enabledTriggerEventName still == 'insert' (since you didn't re-run that isInitialized block), so you fail this else if statement

That explains why your insert is working, but your update is not...or rather, why your first DML call will work, but all subsequent DML calls that use your framework (and the same type of DML, e.g. insert/update/delete) will fail.

I haven't completely digested your code (and I have my own work to attend to), but if I had to take a guess, I'd guess that the initialization block in your constructor is to handle cases where you have more than 200 records being DML'd (and Salesforce breaks them up into chunks with one execution per 200 records). If that's the case, fixing this will entail finding a better mechanism to control what happens in that situation.

To be useful, your framework will absolutely need to change enabledTriggerEventName, enabledBeforeHandlerClassName, and enabledAfterHandlerClassName more than once.

Related Topic