1

I am attempting to cover for apex code that compares if a particular value on the Opportunity object has increased since the Opportunity was updated. This is a before update trigger, which may be a reason why my test fails to cover the code.

Each Opportunity may have the Resubmit_Counter__c field begin as null and be updated to be 1 and greater after each edit. I've initialized an Opportunity record to have Resubmit Counter as null in the test class, and updated the field to be 2. However, the lines in the trigger that execute when the Resubmit_Counter__c is not null do not get covered.

The Trigger:

Trigger updateContracts on Opportunity (before update) {
    // Creating two distinct lists to prevent collision where same Contract enters expiration and resubmission (update)
    Contract[] expiredContracts = new List<Contract>();
    Contract[] updatedContracts = new List<Contract>();
    Opportunity o = Trigger.new[0];  
    List<Contract> contracts = [SELECT Id, SBQQ__Opportunity__c, Contract_Status__c, Primary_Quote_Start_Date__c
                                FROM Contract WHERE SBQQ__Opportunity__c =: o.Id AND Contract_Status__c != 'Expired' ORDER BY CreatedDate DESC];

    List<Contract> amendedContract = [SELECT Id FROM Contract WHERE Id =: o.SBQQ__AmendedContract__c LIMIT 1];

    for(Opportunity o: Trigger.New){
        if(o.Amendment_Opportunity__c == false){
            for(contract c : contracts){
                // hold most recent contract in a variable  
                Contract latestContract = contracts[0];

                // check if it's the latest contract, skip over the latest contract
                if(c <> latestContract && o.Submission_Counter__c > 0){
                    c.Contract_Status__c  = 'Expired';
                    expiredContracts.add(c);
                    // update the     
                }if(c == latestContract && o.Resubmit_Counter__c > 0 && o.Resubmit_Counter__c <> null && o.Resubmit_Counter__c > trigger.oldMap.get(o.Id).Resubmit_Counter__c){ // Resubmit to Checkout has been clicked                     
                    c.StartDate = c.Primary_Quote_Start_Date__c;
                    c.Submit_Email_Notice_Confirmation__c = true; 
                    updatedContracts.add(c); 
                }if(c == latestContract){
                    c.Resubmit_Resend_Email_Notice__c  = true; 
                }
            }
        }

          // update existing Contract on Resubmit without creating new lines 
          else if(o.Amendment_Opportunity__c == true){
                  Contract amendmentContract = amendedContract[0];    
               // increment Amendment Counter on Contract to delete existing lines and CPQ package will create new lines 
               if(o.StageName == 'CPQ - Closed Won' && ((o.Submission_Counter__c > trigger.oldMap.get(o.Id).Submission_Counter__c)) || (o.Submission_Counter__c == 1 && trigger.oldMap.get(o.Id).Submission_Counter__c == null)){
                  amendmentContract.Amendment_Counter__c = o.Amendment_Opp_Amendment_Counter__c; 
                  amendmentContract.Amendment_Expiration_Date__c = o.Primary_Quote_Expiration_Date__c;
                  amendmentContract.Amendment_Text_For_Submission_eMail__c = o.Text_for_Email_Alert__c; 
                  amendmentContract.Primary_Contact_eMail__c = o.Primary_Contact_Email__c;
                  amendmentContract.Submit_Email_Notice_Confirmation__c = true; 
                  amendmentContract.Quote_Action_Type_Amendment__c = 'Amendment';
                  amendmentContract.Amendment_Sub_Status__c = 'Pending Amendment Checkout'; 
                  amendmentContract.Offline_Migration_Deal_Amendment__c = o.Offline_Migration_Deal_Picklist__c;  
                  update amendmentContract; 
               }
               // If Amendment Opportunity is resubmitted, update checkbox that will trigger email update 
               if(o.StageName == 'CPQ - Closed Won' && ((o.Resubmit_Counter__c > trigger.oldMap.get(o.Id).Resubmit_Counter__c) || o.Resubmit_Counter__c == 1)){
                  amendmentContract.Amendment_Counter__c = o.Amendment_Opp_Amendment_Counter__c;
                  amendmentContract.Amendment_Text_For_Submission_eMail__c = o.Text_for_Email_Alert__c; 
                  update amendmentContract; 
               }
            }
    }
    if(updatedContracts != null){
        update updatedContracts;
    }
    if(expiredContracts != null){
        update expiredContracts;
    }
}

The Test Class:

@isTest 
private class updateContractsTestClass {
    static testMethod void validateOriginalOpportunities() {
        Opportunity opp = new Opportunity(Submission_Counter__c = 1, 
                                            Resubmit_Counter__c = null, 
                                            Name='ColorB', 
                                            StageName='CPQ - Closed Won', 
                                            Probability = 80, 
                                            CloseDate=Date.newInstance(1960, 2, 17)); 
          insert opp; 

          Opportunity opp2 = new Opportunity(Resubmit_Counter__c = null, 
                                             Submission_Counter__c = 1, 
                                             Name='ColorC', 
                                             StageName='CPQ - Closed Won', 
                                             Probability = 80, 
                                             CloseDate=Date.newInstance(1960, 2, 17)); 
          insert opp2;

          SBQQ__Quote__c quote = new SBQQ__Quote__c(SBQQ__Opportunity2__c = opp.id, 
                                             SBQQ__StartDate__c = Date.newInstance(2017, 3, 27), 
                                             SBQQ__Primary__c = true,
                                             SBQQ__SubscriptionTerm__c = 12);  
          insert quote; 

          SBQQ__Quote__c quote1 = new SBQQ__Quote__c(SBQQ__Opportunity2__c = opp2.id, 
                                             SBQQ__StartDate__c = Date.newInstance(2017, 2, 17), 
                                             SBQQ__Primary__c = true,
                                             SBQQ__SubscriptionTerm__c = 12);  
          insert quote1; 

          opp.SBQQ__PrimaryQuote__c = quote.id;      
          opp.Resubmit_Counter__c = 2;     
          opp2.SBQQ__PrimaryQuote__c = quote1.id; 
          opp2.Resubmit_Counter__c = 2;
          opp = [SELECT id, SBQQ__PrimaryQuote__r.SBQQ__StartDate__c FROM Opportunity WHERE Name = 'ColorB'];

          opp2 = [SELECT id FROM Opportunity WHERE Name = 'ColorC'];

          // inserting Account to then reference it in the Contract (required by contract)
          Account testAccount = new Account(Name='testAccount'); 
          insert testAccount; 
          Id accountId = [SELECT Id FROM Account LIMIT 1].Id;   

          //List<Contract> contractsList = new List<Contract>();     
          //insert contractsList; 

          Contract firstContract = new Contract (AccountID = accountId, SBQQ__Opportunity__c = opp2.id,
                                                 Contract_Status__c = 'Pending Checkout', Amendment_Counter__c = 1); 
          insert firstContract; 
          firstContract.Contract_Status__c = 'Expired'; 
          update firstContract;

          Contract secondContract = new Contract (AccountID = accountId, SBQQ__Opportunity__c = opp2.id,
                                                 Contract_Status__c = 'Pending Checkout'); 
          insert secondContract;    

          Contract latestContract = new Contract(AccountID = accountId, SBQQ__Opportunity__c = opp.id, 
                                                 Contract_Status__c = 'Pending Checkout'); 
          insert latestContract; 
          update latestContract;

          // Update the contract that is not the latest to be expired 
          firstContract.Contract_Status__c = 'Expired'; 
          update firstContract; 

          // update Opportunities after contracts are inserted to cover the Contracts for loop 
          test.startTest(); 
          update opp; 
          update opp2;    
          update latestContract; 
          test.stopTest(); 
          // handle Amendment Opportunities 
          Opportunity amendmentOpp = new Opportunity(Submission_Counter__c = 1, 
                                          Resubmit_Counter__c = 1, 
                                          Offline_Migration_Deal_Picklist__c = 'No', 
                                          SBQQ__AmendedContract__c = firstContract.Id,
                                          Name='AmendmentOpp', 
                                          StageName='1 - Qualification', 
                                          CloseDate=Date.newInstance(1960, 2, 17)); 
         insert amendmentOpp; 
         amendmentOpp.Submission_Counter__c = 2; 
         amendmentOpp.Resubmit_Counter__c = 2; 
         update amendmentOpp; 

         Opportunity closedAmendmentOpp = new Opportunity(Submission_Counter__c = 1, 
                                          Resubmit_Counter__c = 1, 
                                          SBQQ__AmendedContract__c = latestContract.Id,
                                          Name='ClosedAmendmentOpp', 
                                          StageName='CPQ - Closed Won', 
                                          CloseDate=Date.newInstance(1960, 2, 17)); 
         insert closedAmendmentOpp;
  }
}

I found this related post on the subject where oldMap.get is used. How to cover trigger.oldmap.get area in test class?

My question: how do I best cover for the code inside this inequality? I know for certain the problem is related to oldMap.get because when I take this feature out, the code inside is covered. (c == latestContract && o.Resubmit_Counter__c > 0 && o.Resubmit_Counter__c <> null && o.Resubmit_Counter__c > trigger.oldMap.get(o.Id).Resubmit_Counter__c)

1 Answer 1

1

There are a collection of things I see that I would improve, but I'll try to keep the scope of my answer narrowly tailored to your question (at least at first).

The fundamental rule of unit testing is: you only gain coverage for code that is executed as part of a test method. I'll also mention that the primary goal of unit testing is not gaining code coverage, but rather validating the results of executing the code you're testing. If you take that approach, and test a variety of possible input scenarios (both positive cases, where input is what you expect, and negative cases, where the input you give to your code is deliberately wrong), the code coverage will naturally be high.

Getting back to your coverage question now, since you're not getting coverage, your code isn't being executed. When you know that, the next question to answer is why isn't this code being executed?

Let's focus on just the if statement in question:

if(c == latestContract && o.Resubmit_Counter__c > 0 && o.Resubmit_Counter__c <> null && o.Resubmit_Counter__c > trigger.oldMap.get(o.Id).Resubmit_Counter__c){ // Resubmit to Checkout has been clicked

so o.Resubmit_Counter__c must be:

  • greater than 0
  • not null
  • current value must be greater than the old value

I'll note that the null check here doesn't accomplish much. if statements are evaluated left-to-right, and exhibits 'short-circuiting' (if we get to a point where we know what the end result will be, the rest of the expression is not evaluated). If o.Resubmit_Counter__c > 0 is true, then it is obviously not null. If o.Resubmit_Counter__c > 0 is false (which would be the case if Resubmit_Counter__c is null as well), then the if statement short-circuits (false && anything == false). If you're going to do a null check, it's generally pretty much the first thing you want to check (but again, there isn't much point in the null check here)

Now let's look at a specific part of your test class:

opp.SBQQ__PrimaryQuote__c = quote.id;      
opp.Resubmit_Counter__c = 2;     
opp2.SBQQ__PrimaryQuote__c = quote1.id; 
opp2.Resubmit_Counter__c = 2;
opp = [SELECT id, SBQQ__PrimaryQuote__r.SBQQ__StartDate__c FROM Opportunity WHERE Name = 'ColorB'];

opp2 = [SELECT id FROM Opportunity WHERE Name = 'ColorC'];

I presume that the initial value for Resubmit_Counter__c is null. You're setting the counter to 2 for both of your opps, but then you overwrite that when you reassign opp and opp2 based on results of the queries.

The value of Resubmit_Counter__c that you set never makes it into the database.

When you run your test proper, o.Resubmit_Counter__c is null, and so is trigger.oldmap.get(o.Id).Resubmit_Counter__c. That explains why you aren't entering that particular if block in your test.

Fixing that requires two additional steps:

  1. You need to ensure that some positive value for Resubmit_Counter__c is explicitly set (and do this pretty early in your test setup). This is because comparing any value against null will result in false.
  2. You need to ensure that Resubmit_Counter__c is being changed for the instance of whatever Opportunity you're updating as part of the "execution" phase of your unit test

#1 could be taken care of in your test setup (the first phase of any unit test) when you insert your Opportunities. Just ensure that you're explicitly setting non-null values for Resubmit_Counter__c.

#2 can be taken care of by not re-querying your Opportunities after setting Resubmit_Counter__c.

as an aside

One of the first lines of your trigger Opportunity o = trigger.new[0]; concerns me.

Your loop is over trigger.new, and you redeclare the o variable (isn't that giving you a compile error?) so you aren't only processing the first Opportunity...but the Contracts that you're working with are only those related to your first Opportunity.

That doesn't feel right to me (at least, not with the information you've given us). If each Opportunity does have its own collection of related Contracts, and you want your logic to work based solely on the Contracts related to the specific Opportunity you're working with in each loop iteration, then you should probably consider using a parent-child subquery instead of limiting the Contracts you query to just the ones related to the first Opportunity in your trigger. Something like...

[SELECT Id, <other fields>, (SELECT Id, <other fields> FROM Contracts WHERE Contract_Status__c != 'Expired' ORDER BY CreatedDate DESC) FROM Opportunity WHERE Id IN :Trigger.new];

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .