0

I am trying to develop a test class to cover a trigger but I am only able to get to 64%. I'm not sure how to get the lines below covered. Can anyone advise how I can increase coverage?  Specifically, the lines not being covered below are:

Lines 13-16

for(VIP_Type__c vip : Trigger.old){
    acctIds.add(vip.Account__c);
    contIds.add(vip.Contact__c);
    contactTypeMapDel.put(vip.Contact__c , new set<String>());
}

Lines 36-41

set<string> existingTypes = new set<string>();
if(existingTypeMap.containsKey(vip.Contact__c)){
    existingTypes = existingTypeMap.get(vip.Contact__c);
}
existingTypes.add(vip.VIP_Type__c);
existingTypeMap.put(vip.Contact__c,existingTypes);

Lines 47-56

if (existingTypeMap.get(vip.contact__c).contains(vip.VIP_Type__c)){
system.debug('@@@### - Check Map:  '+existingTypeMap.get(vip.contact__c));
system.debug('@@@### - Check VIP Type:  '+vip.VIP_Type__c);
    trigger.newMap.get(vip.Id).addError('This Type already exists for this Contact');
}
else
{
    Set<string> existingTypes = existingTypeMap.get(vip.Contact__c);
    existingTypes.add(vip.VIP_Type__c);
    existingTypeMap.put(vip.Contact__c,existingTypes);
}

Test Class:

@isTest
private class TestVIP_Types_Trigger {
        
    Private static testMethod void vipScenarioTests() {
            
        Test.startTest();
            
        Account testAccount = test_CreateRecords.createAcct(0);
        insert testAccount;
        
        Contact testContact = test_CreateRecords.createCont(testAccount.Id);
        insert testContact;
        
        Contact testContact2 = test_CreateRecords.createCont2(testAccount.Id);
        insert testContact2;
            
            VIP_Type__c testVIP1 = new VIP_Type__c();
                testVIP1.Account__c = testAccount.Id;
                testVIP1.Contact__c = testContact.Id;
                testVIP1.VIP_Type__c = 'Ambassador';
            insert testVIP1;
    
            VIP_Type__c testVIP2 = new VIP_Type__c();
                testVIP2.Account__c = testAccount.Id;
                testVIP2.Contact__c = testContact.Id;
                testVIP2.VIP_Type__c = 'Influencer';
            insert testVIP2;
    
            Boolean errThrown = false;
            try{
            VIP_Type__c testVIP3 = new VIP_Type__c();
                testVIP3.Account__c = testAccount.Id;
                testVIP3.Contact__c = testContact.Id;
                testVIP3.VIP_Type__c = 'Ambassador';
            insert testVIP3;
            }catch(Exception e) {
                errThrown = true;
                    Boolean expErr = (e.getMessage().contains('This Type already exists')) ? true : false;
            System.assertEquals(true,expErr, e.getMessage());
            }
            System.assertEquals(false,errThrown,'No exception was thrown');
            
    
            Boolean errThrown2 = false;
            try{
            testVIP2=[SELECT Id, VIP_Type__c FROM VIP_Type__c WHERE Id =: testVIP2.Id];
                testVIP2.VIP_Type__c = 'Ambassador';
            update testVIP2;
            }catch(Exception e) {
                errThrown2 = true;
                    Boolean expErr2 = (e.getMessage().contains('This Type already exists')) ? true : false;
            System.assertEquals(true,expErr2, e.getMessage());
            }
            System.assertEquals(false,errThrown2,'No exception was thrown');
    
            testVIP2=[SELECT Id, VIP_Type__c FROM VIP_Type__c WHERE Id =: testVIP2.Id];
                testVIP2.VIP_Type__c = 'Celebrity';
            update testVIP2;
    
            VIP_Type__c testVIP1a = new VIP_Type__c();
                testVIP1a.Account__c = testAccount.Id;
                testVIP1a.Contact__c = testContact2.Id;
                testVIP1a.VIP_Type__c = 'Ambassador';
            insert testVIP1a;
    
            VIP_Type__c testVIP2a = new VIP_Type__c();
                testVIP2a.Account__c = testAccount.Id;
                testVIP2a.Contact__c = testContact2.Id;
                testVIP2a.VIP_Type__c = 'Celebrity';
            insert testVIP2a;
    
            delete testVIP1;
            delete testContact;
            delete testAccount;
    Test.stopTest();
    }
}

Trigger:

trigger UpdateAcctVIP on VIP_Type__c (after insert, after update, after delete){
    
     if(checkRecursive.runOnce())
        {
        Set<Id> acctIds = new Set<ID>();
        Set<Id> contIds = new Set<ID>();
    //    Set<String> vipTypes = new Set<String>();
        Map<VIP_Type__c, VIP_Type__c> contactTypeMap = new Map<VIP_Type__c, VIP_Type__c>();
        Map<Id, Set<String>> contactTypeMapDel = new Map<Id, Set<String>>();
        
    // Get all the Account & Contact Ids in the Set 
        if (Trigger.isDelete) {
            for(VIP_Type__c vip : Trigger.old){
                acctIds.add(vip.Account__c);
                contIds.add(vip.Contact__c);
                contactTypeMapDel.put(vip.Contact__c , new set<String>());
    //            contactTypeMap.put(vip.Contact__c, vip.VIP_Type__c);
            }
        }
        else{
    
            for(VIP_Type__c vip : Trigger.new) {
                acctIds.add(vip.Account__c);
                contIds.add(vip.Contact__c);
            }
    //   }
    
        List<VIP_Type__c> vipRecs = [SELECT Id,Account__c,Contact__c,VIP_Type__c
                                FROM VIP_Type__c
                                WHERE Contact__c = :contIds AND ID NOT IN:trigger.new];
    
    //Check for VIP Types already entered for Contact
        Map<Id,set<string>> existingTypeMap = new Map<Id,set<string>>();
        
        for(VIP_Type__c vip: vipRecs) {
            set<string> existingTypes = new set<string>();
            if(existingTypeMap.containsKey(vip.Contact__c)){
                existingTypes = existingTypeMap.get(vip.Contact__c);
            }
            existingTypes.add(vip.VIP_Type__c);
            existingTypeMap.put(vip.Contact__c,existingTypes);
        }
    
        for (VIP_Type__c vip : trigger.new)
        {
            if (existingTypeMap.containsKey(vip.Contact__c)){
                if (existingTypeMap.get(vip.contact__c).contains(vip.VIP_Type__c)){
    system.debug('@@@### - Check Map:  '+existingTypeMap.get(vip.contact__c));
    system.debug('@@@### - Check VIP Type:  '+vip.VIP_Type__c);
                    trigger.newMap.get(vip.Id).addError('This Type already exists for this Contact');
                }
                else
                {
                    Set<string> existingTypes = existingTypeMap.get(vip.Contact__c);
                    existingTypes.add(vip.VIP_Type__c);
                    existingTypeMap.put(vip.Contact__c,existingTypes);
                }
    
            }
        } 
        }
    
    
    // Query the Accounts
            List<Account> acct = new List<Account>();
    
    // Use the VIP Types to get all the related Types for the Account 
                acct = [SELECT Id, VIP_Types__c,(Select VIP_Type__c FROM VIP_Types__r ORDER BY VIP_Type__c) 
                        FROM Account 
                        WHERE Id in :acctIds]; 
    
    // Iterate over each Account and VIP record 
                for(Account a : acct){ 
                    a.VIP_Types__c = ''; 
    
                    for(VIP_Type__c vip: a.VIP_Types__r){ 
    
                        if(!a.VIP_Types__c.contains(vip.VIP_Type__c) || a.VIP_Types__c == ''){ // Check if the Type is already in the Account Field. if not add it otherwise skip 
    
                        a.VIP_Types__c += vip.VIP_Type__c + '; '; 
                        } 
                    } 
                } 
    // Update the Account 
            update acct; 
    }
}
5
  • 2
    I haven't looked yet to see if it is related to your issue, but checkRecursive.runOnce() is a very dangerous pattern that will cause a variety of unexpected and negative behaviors in your triggers.
    – David Reed
    Sep 9 '20 at 15:56
  • 1
    Most likely, given that most of the code being missed is inside of for-loops that are only entered based on the action of the trigger (ex. you can only iterate Trigger.old in update and delete calls) I think checkRecursive.runOnce() is messing with your outcomes. Also, I would write a unit test for each action - insert, update, and delete. - and make some assertions. Otherwise you're testing nothing.
    – nbrown
    Sep 9 '20 at 15:59
  • 2
    You should read through How do I increase my code coverage, or why can't I cover these lines? if you haven't already. It contains the common advice for questions like these.
    – Derek F
    Sep 9 '20 at 16:00
  • 2
    Does this answer your question? How do I increase my code coverage, or why can't I cover these lines?
    – identigral
    Sep 9 '20 at 16:35
  • Thanks all. The checkRecursive line was the culprit. I removed and now have 100% coverage. Thanks so much. Dvid, can you add you comment as an answer so I can select it as solved?
    – J. Neilan
    Sep 9 '20 at 16:40
3

checkRecursive.runOnce() is a problem for lots of reasons:

  • It forces your trigger to run exactly once per transaction.
  • As a result, your trigger will run for only one event per transaction, and only for the first DML operation or first batch of 200 records for which it is invoked.
  • An insert followed by an update or delete will not correctly run the trigger for subsequent events.
  • Inserting more than one record in a transaction in separate DML operations will result in the trigger not running for the second record.

etc.

Since you have written only one unit test (one transaction) which performs many DML operations, your trigger won't run for most of the test, which explains negative behavior.

In addition to removing this non-functional recursion guard, consider factoring your trigger into a handler class to be clearer about which code runs on which event, and writing multiple unit tests to independently validate each code pathway or event. Each unit test should include meaningful assertions to validate the behavior of your code.

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.