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
Kelly Logan (Raptek)Kelly Logan (Raptek) 

TDTM Trigger Handler fires from UI but not from test class

Working in NPSP, so I have a concurrent post in the Hub, but I'm thinking this may be more test class related so I'm posting here as well.

I have a simple service class that modifies a couple dates
public class VIR_ContactService {

    public static void UpdateTACertDates(List<Contact> newCons){
        for (Contact c : newCons) {
            if (c.TA_Join_Date__c != null) {
                if (c.TA_Date_Recertified__c == null || c.TA_Certification_Expiration__c == null) {
                    c.TA_Date_Recertified__c = c.TA_Join_Date__c;
                    c.TA_Certification_Expiration__c = c.TA_Join_Date__c.addYears(3);
                }
                if (c.TA_Certification_Expiration__c <= System.Date.today()) {
                    c.TA_Certification_Expiration__c = c.TA_Certification_Expiration__c.addYears(3);
                    c.TA_Date_Recertified__c = c.TA_Certification_Expiration__c.addYears(-3);
                }
            }
        }
    }

}

This is called by a TDTM trigger handler
global without sharing class VIR_CallTAUpdateCertDates_TDTM extends npsp.TDTM_Runnable {
  
  // the main entry point for TDTM to invoke our trigger handlers.
  global override npsp.TDTM_Runnable.DmlWrapper run(List<SObject> newlist, List<SObject> oldlist, 
             npsp.TDTM_Runnable.Action triggerAction, Schema.DescribeSObjectResult objResult) {
  
  	npsp.TDTM_Runnable.DmlWrapper dmlWrapper = null;

      if (triggerAction == npsp.TDTM_Runnable.Action.BeforeInsert ||
         triggerAction == npsp.TDTM_Runnable.Action.BeforeUpdate ) {
        List<Contact> newConList = (List<Contact>)newlist;
        VIR_ContactService.UpdateTACertDates(newConList);
      }
  	return dmlWrapper;
  	}
}
This works fine from the UI. I can update TA_Certification_Expiration__c on a Contact and the values will be modified as expected. The problem occurred when I created a test class.
 
@isTest
class VIR_ContactService_test {

    @isTest
    static void UpdateTACertDates_test () {
        Account a = new Account(Name='Test Vendor');
        insert a;
        
        List<Contact> conList = new List<Contact>();
        for(Integer i=0;i<3;i++){
            Contact c = new Contact(LastName='Tester'+i,Account=a);
            conList.add(c);
        }
        Date now = System.Date.today();
        conList[1].TA_Join_Date__c = now.addYears(-1);
        System.debug('Test Contacts for insert: ' + conList);
        Test.startTest();
        insert conList;

		conList = [SELECT Id,FirstName,LastName,TA_Join_Date__c,
                   TA_Date_Recertified__c,TA_Certification_Expiration__c
                   FROM Contact
                   ORDER BY LastName];
      
        conList[0].FirstName = 'Fatima';
        conList[1].FirstName = 'Frank';
        conList[1].TA_Certification_Expiration__c = now.addYears(-2);
        conList[2].FirstName = 'Fiora';
        conList[2].TA_Certification_Expiration__c = now.addYears(-1);
        System.debug('Test Contacts for update: ' + conList);
        update conList;
        
		conList = [SELECT Id,FirstName,LastName,TA_Join_Date__c,
                   TA_Date_Recertified__c,TA_Certification_Expiration__c
                   FROM Contact];

        system.assertEquals(null, conList[0].TA_Certification_Expiration__c);
        system.assertEquals(now.addYears(1), conList[1].TA_Certification_Expiration__c);
        system.assertEquals(now.addYears(2), conList[2].TA_Certification_Expiration__c);
        Test.stopTest)();
    }
}
The insert triggers and runs the service class, but the update does not. What am I missing? Is there some aspect of a test class update that is significantly different here that updates work from the UI but not in the test run?
 
Zuinglio Lopes Ribeiro JúniorZuinglio Lopes Ribeiro Júnior
Hello Kelly,

It is supposed to work in Test Classes as well. However, the problem might be because when you first retrieve the Contact list you're using a ORDER BY criteria and in the second time you're not. So the records are in different positions compared to your previous select.
 
@isTest
class VIR_ContactService_test {

    @isTest
    static void UpdateTACertDates_test () {
&nbsp; &nbsp; &nbsp; &nbsp; Account a = new Account(Name='Test Vendor');
&nbsp; &nbsp; &nbsp; &nbsp; insert a;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; List<Contact> conList = new List<Contact>();
&nbsp; &nbsp; &nbsp; &nbsp; for(Integer i=0;i<3;i++){
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Contact c = new Contact(LastName='Tester'+i,Account=a);
            conList.add(c);
        }
        Date now = System.Date.today();
        conList[1].TA_Join_Date__c = now.addYears(-1);
        System.debug('Test Contacts for insert: ' + conList);
        Test.startTest();
        insert conList;

		conList = [SELECT Id,FirstName,LastName,TA_Join_Date__c,
                   TA_Date_Recertified__c,TA_Certification_Expiration__c
                   FROM Contact
                   ORDER BY LastName];
      
        conList[0].FirstName = 'Fatima';
        conList[1].FirstName = 'Frank';
        conList[1].TA_Certification_Expiration__c = now.addYears(-2);
        conList[2].FirstName = 'Fiora';
        conList[2].TA_Certification_Expiration__c = now.addYears(-1);
        System.debug('Test Contacts for update: ' + conList);
        update conList;
        
		conList = [SELECT Id,FirstName,LastName,TA_Join_Date__c,
                   TA_Date_Recertified__c,TA_Certification_Expiration__c
                   FROM Contact ORDER BY LastName];

        system.assertEquals(null, conList[0].TA_Certification_Expiration__c);
        system.assertEquals(now.addYears(1), conList[1].TA_Certification_Expiration__c);
        system.assertEquals(now.addYears(2), conList[2].TA_Certification_Expiration__c);
        Test.stopTest)();
    }
}

Hope to have helped!

Regards.

Don't forget to mark your thread as 'SOLVED' with the answer that best helps you.
 
Kelly Logan (Raptek)Kelly Logan (Raptek)
Good catch Zuinglio! I fixed that but it looks like TDTM might be the issue after all. I'm trying to get some clarification but it looks like I need to muck with the trigger handler defs in the test class as well.
Zuinglio Lopes Ribeiro JúniorZuinglio Lopes Ribeiro Júnior
Hello Kelly,

Great! I'm use a different handler so in this one you're alone unless you want to share the code. Some trigger frameworks use Custom Settings to aid its functionality and if that's the case, you should also create the necessary custom setting records in your test class as well.

Furthermore, looking carefully to your test class I would say that as Lists are ordered if you remove your first query conList and remove the order by from the last one the test class should work just as fine.

Hope to have helped!

Regards.

Don't forget to mark your thread as 'SOLVED' with the answer that best helps you.
 
Kelly Logan (Raptek)Kelly Logan (Raptek)
It turns out that the NonProfit Success Pack (NPSP)'s use of TDTM for trigger handling means a bit of extra work for testing. The list of trigger handlers kept in Trigger_Handler__c is not exposed to the secured test environment. TDTM does generate the defaults for NPSP trigger handlers, but any custom trigger handlers need to be added to that list manually in the test class. I'm trying to get the proper procedure from Sf to do this, because my attempt was not correct.

Here's the odd thing though - I can see in the logs for VIR_ContactService_test that the trigger handler is called for the insert (line 18), but nothing is called for the update (line 31). If the trigger handler is not registered at all, why would it work for insert, but not for update? That makes me think that there's something different about the BeforeUpdate event that I didn't compensate for. I thought the only difference would be in the trigger handler where I need to check for both events because I have it call the same class.method the same way for both.
Jennifer ChanceJennifer Chance
Kelly,

I know this thread is quite old but do you have an example of what you did to add the trigger handler to the test class? I can't find any examples.

Thanks!