2

I am writing a test class to a handler class called from a trigger. And due to recursive trigger I had to implement CheckRecursive in the trigger and this works fine, until I start testing. Then the trigger only runs once even after I use Test.startTest() and Test.stopTest.

This is my trigger

if(trigger.isInsert){
    if(checkRecursive.contactAfterInsertTriggerRunOnce()){
        ContactHandler.callAmethod(trigger.newMap.keyset());
    }
} if(trigger.isUpdate){
if(checkRecursive.contactAfterUpdateTriggerRunOnce()){
    ContactHandler.createContactListToSendToForUpdate(trigger.new, trigger.oldMap);       
    ContactHandler.contactMergeLearners(trigger.new);
    }
} if(trigger.isDelete){
    if(checkRecursive.contactAfterDeleteTriggerRunOnce()){
        ContactHandler.deleteUsersList(trigger.old);
    }
}

This is the checkRecursive class

public Class checkRecursive{
contactAfterInsertTriggerRun
public static boolean contactAfterInsertTriggerRunOnce(){
    if(contactAfterInsertTriggerRun){
        contactAfterInsertTriggerRun=false;
        return true;
    } else {
    return contactAfterInsertTriggerRun;
    }
}

And this is my test class

@isTest
private class ContactHandlerTest {

    @testSetup static void setupTestData(){


        list<Account> accList = new list<Account>();

        Account stdAcc = TestFactory.getAccount('Standard testAcc');
        accList.add(stdAcc);

        Account reselAcc = TestFactory.getAccount('Reseller testAcc');
        reselAcc.Reseller__c = 'Partner';
        accList.add(reselAcc);

        Account distributorAcc = TestFactory.getAccount('Distributor testAcc');
        distributorAcc.Distributor__c = 'Partner';
        accList.add(distributorAcc);

        insert accList;

    }

    static testMethod void testCreateContacts() {
        // Get account from test
        Account acc = [SELECT Id, Reseller__c, Distributor__c FROM Account WHERE Reseller__c = '' AND Distributor__c = '' LIMIT 1];

        // Create an list for contacts
        list<Contact> contactListBeforeDelete = new list<Contact>(); 
        contactListBeforeDelete = [SELECT Id FROM Contact];
        system.assertEquals(0, contactListBeforeDelete.size(), 'There are ' + contactListBeforeDelete.size() + ' contacts there should be 0 before delete');

        Test.startTest();
        Contact stdContact = testFactory.getContact(acc, 'Standard');
        insert stdContact;
        Test.stopTest();

        list<Contact> contactListAfterDelete = new list<Contact>(); 
        contactListAfterDelete = [SELECT Id FROM Contact];
        system.assertEquals(1, contactListAfterDelete.size(), 'There are ' + contactListAfterDelete.size() + ' contacts there should be 1 after delete');
    }

My problem is that the trigger is never run more than once. When I update the the contact the trigger does not run again and I do not understand why. Can any of you spot an error? Or is it just very difficult to test a trigger with a recursive check in it. I thourght about inserting the contact in the @testSetup and then just updating it. But I just don't understand why that should be nessary.

5

First of all, using a simple Boolean variable for recursion control is a flawed approach if you ever will update in a transaction more than 200 records (like in some controller/REST service/execute anonymous/platform event trigger). A better solution involves managing a set of IDs and checking to see if the ID has been 'visited' before to see if you should ignore it during the 2nd, 3rd, ... trigger execution. You can read more about this here and sfdcfox's answer in the SFDC Forum here.

As to the testmethod issue -- after you do DML on a trigger-recursion-controlled sobject, you need to clear the recursion "flags" as if a new user transaction were occuring. That is, in your case, the static variable have to go back to their initialized state.

Add to your recursion control class some sort of reset() method to clear the "flags"

  • Hi Cropredy, thank you for you comment. I see what you mean now and I will try and implement that. I was under te assumption that using testStart and testStop would reset these values so that all triggers would run again. I got the solution from this KA on salesforce: help.salesforce.com/apex/… I will let you know if this works. Thank you. – ChristianJuul Apr 12 '16 at 7:52
  • Adding a reset() function worked. I just do not understand why it is nessary to do so in tests, when the code runs fine outside unit tests. – ChristianJuul Apr 12 '16 at 10:01
  • @ChristianJuul - when the code runs outside of tests, each user 'save' action on a record is a separate transaction and static variables are reset between transactions. In a testmethod, the entire method is a transaction, including setup of test sobjects – cropredy Apr 12 '16 at 13:36
0

You only included part of your trigger, but the part you included indicates that your trigger is an Insert Trigger, not an Update trigger:

if(trigger.isInsert){

You need to either change the above line, or have another line someplace else that looks like what's below along with the supporting code below it that you want to run:

if(trigger.isUpdate){
  • Thank you for your comment, I tried to make it simple and cut out some code. I have now added the update and the delete. – ChristianJuul Apr 12 '16 at 7:47
0

Since you haven't included the code for your ContactHandler class, it's difficult to say what the best approach is.

However, in the past when I've wanted to test some functionality that a user would experience as multiple separate transactions (e.g. inserting a contact and then updating it with different field values) I've gotten around the recursion prevention issue by using the @TestVisible annotation.

My trigger handler class would set a Boolean to false after the logic has processed in order to prevent recursion. However, in my test class, where a real-life user would initiate a new transaction, I reset the value back to true. Dummy code below:

public class ContactHandler {

    @TestVisible private static Boolean runMethod = true;

    public static void someMethod (List<Contact> new Contacts){
        if(runMethod == true){
            //execute logic
        }
        runMethod = false;
    }

}

@isTest
private class TestContactHandler {

    private static void someTestMethod {
        Contact c = new Contact();
        insert c;

        ContactHandler.runMethod = true;

        c.LastName = 'SomeLastName';
        update c;
    }

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.