This class is based on Dan Appleman's TriggerDispatcher process, and is called from a centralized (TriggerDispatcher) class based on the SObject (Case) and the trigger type (Before Update). From the UI, when the Case is updated, the related Object__c (name changed to protect the innocent) records are retrieved and the associated Case (picklist, as it happens) fields are set accordingly in the before trigger.

When I run an update scenario in my test class, however, the process neatly avoids touching the trigger class at all (verified with debug statements that fire in test class but do not show trigger, triggerdispatcher or trigger method' debug statements. I removed the statements from the code (below) to conserve space.).

public class className implements ITriggerEntry{

public void MainEntry(String TriggerObject, Boolean IsBefore, Boolean IsDelete, Boolean IsAfter, Boolean IsInsert, Boolean IsUpdate, Boolean IsExecuting, List<SObject> newList, Map<Id, SObject> newMap, List<SObject> oldList, Map<Id, SObject> oldMap) {

    // Cast the Objects to the appropriate type
    List<Case> cases = (List<Case>) newList;
    List<Case> oldCases = (List<Case>) oldList;
    Map<Id, Case> triggerNew = (Map<Id, Case>) newMap;
    Map<Id, Case> triggerOld = (Map<Id, Case>) oldMap;

    // Identify related records, 
    // If changed in the last day, update the related Case
    Set<Id> setCaseIds = new Set<Id>();
    for(Case cs : cases) {

    // Retrieve associated records with all fields
    List<Object__c> objects = [Select ...];

    // Add them to a map by Case Id
    Map<Id, List<Object__c>> mapRecordsByCaseId = new Map<Id, List<Object__c>>();
    List<Object__c> innerRecords;
    for(Object__c o : objects) {
        innerRecords= mapRecordsByCaseId.get(o.Case_Number__c);
        if(innerRecords== null) innerRecords= new List<Object__c>();
        mapRecordsByCaseId.put(o.Case_Number__c, innerRecords);

    String products2bShipped;   //list
    String products2bReturned;  //list

    for(Case cs : cases) {
        // reset the field variables for each Case
        products2bShipped = '';
        products2bReturned = '';

        innerRecords = mapReturnsByCaseId.get(cs.Id);
        if(innerRecords <> null && innerRecords .size() > 0) {

            for(Object__c o : innerRecords ) {

                if(o.Shipment_Type__c == 'Shipment') {
                    // build product list
                    products2bShipped = Utils.addToCSV(products2bShipped, o.Product__c);
                } else { // Swap
                    products2bShipped = Utils.addToCSV(products2bShipped, o.Equipment__c);
                    products2bReturned = Utils.addToCSV(products2bReturned, o.Equipment__c);
        // Set the associated values on the Case
        cs.Product_s_to_be_returned__c = products2bReturned;
        cs.List_Supplies_Needed__c = (products2bShipped.length() > 0) ? products2bShipped : null;}

The Handler reference is here:

if(TriggerObject == 'Case') {

if(isBefore && isUpdate) {

System.debug('** Calling NxCaseObjectAssociation() from TriggerDispatcher with ' + Trigger.new.size() + ' cases.');                            
NxCaseObjectAssociation relatedObjAssociate = new NxCaseObjectAssociation();

       Trigger.IsBefore, Trigger.IsDelete, 
       Trigger.IsAfter, Trigger.IsInsert,
       Trigger.IsUpdate, Trigger.IsExecuting,
       Trigger.new, Trigger.newMap,
       Trigger.old, Trigger.oldMap);

Trigger (if it fits):

trigger CaseTrigger on Case (before insert, after insert, before update, after update) { 
    if ((Trigger.isBefore || Trigger.isAfter) && (Trigger.isInsert || Trigger.isUpdate)) { // redundant here

    System.debug('** Calling TriggerDispatcher from CaseTrigger ...');
        trigger.isBefore, trigger.isDelete,
        trigger.isAfter, trigger.isInsert,
        trigger.isUpdate, trigger.isExecuting,
        trigger.new, trigger.newMap, trigger.old, trigger.oldMap);

Test: static testMethod void RelatedShipments() {

    c = [Select Id, AccountId from Contact where LastName = 'Returns, CA'];
    cs = UT_Utils.createCase('Open', c, 'Chronic - Home Patient', 'Console Shell');
    insert cs;      
    // Create some related Objects for the Case
    relatedObjects = TestDataFactory.createRelatedObjects(3, cs.Id, 'Shipment');
    insert relatedObjects;
        cs = UT_Utils.reGetCase(cs.Id);
        cs.Description = 'Description';
        update cs;      

  cs = UT_Utils.reGetCase(cs.Id);
  System.assertNotEquals(null, cs, 'Incorrect result returned, a Case should have been returned: '); // no problem

  Integer i = [Select count() from Object__c where Case_Number__c = :cs.Id];
  System.assertEquals(3, i, 'Incorrect result returned for # of related objects created: ');
   // no problem
  relatedObjects = [Select Shipment_Type__c, Equipment__c, Product__c
                    from   Object__c
                    where  Case_Number__c = :cs.Id];

  System.assertEquals(3, relatedObjects.size(), 'Incorrect result returned for # of related objects retrieved: ');
   // no problem

  for(Object__c o : relatedObjects) {
    System.assertEquals('Shipment', o.Shipment_Type__c, 'Incorrect result returned for Shipment Type ');    
    System.assertEquals('Test Ship', o.Product__c.left(9), 'Incorrect result returned for Returns Object.Product ');

  // no problem, either one ^
  // But now, ASSERT!!
  System.assertNotEquals(null, cs.List_Supplies_Needed__c, 'Incorrect result(s) returned for Products to be shipped ');
  • Can you post the trigger itself? Commented Feb 18, 2016 at 19:43
  • You mentioned that the test doesn't touch the class. How are you verifying that? Are you looking at code coverage? Executing asserts? I think adding the test class would be helpful as well. Commented Feb 18, 2016 at 19:55
  • I've added the test class; adjusting for size didn't quite make the cut, though. I hope there's enough there to work with. Commented Feb 18, 2016 at 21:06
  • 2
    (1) Does the Appleman pattern (TriggerDispatcher.entry(...)) use a master kill switch to enable/disable all trigger - using a custom setting or the like? You'll need to mock that in the testmethod (2) More plausibly, you may have trigger recursion logic that has disabled the update cs from executing. You won't see this in the UI because the Case insert and Case update occur in different transactions
    – cropredy
    Commented Feb 19, 2016 at 20:00
  • Master kill switch no (though I typically add one later, just in cases), but curiously, moving the Case insert() statement inside the startTest()...stopTest() block did the trick! Thanks, @crop1645! Clearly @stevemo is not the only person to whom I owe beer :D Commented Feb 22, 2016 at 13:51


You must log in to answer this question.

Browse other questions tagged .