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
travis.truetttravis.truett 

Trigger No Longer Passes Apex Test After Adding Anti-Recursion

My trigger was throwing some errors when we would try to update opportunities, and it was suggested to me that I create a class to make sure the trigger only runs once, and then reference that class in my trigger. This is the class: 

public Class checkRecursive{
    private static boolean run = true;
    public static boolean runOnce(){
    if(run){
     run=false;
     return true;
    }else{
        return run;
    }
    }
}

When I try to test my code coverage for my trigger now, it fails, and I'm not sure why. He's the code for the trigger:

trigger editSchedule on OpportunityLineItem (after insert) {

if(checkRecursive.runOnce())
    {

if(Trigger.isInsert){

Map<Id, Opportunity> opportunities = new Map<Id, Opportunity>();
Date myDate = Date.newInstance(1990, 2, 17);

for(OpportunityLineItem oli: Trigger.new) {
    opportunities.put(oli.OpportunityId, null);
}
opportunities.putAll([SELECT Contract_Start_Date__c FROM Opportunity WHERE Id in :opportunities.keySet()]);
for(OpportunityLineItem oli: Trigger.new) {
     Opportunity thisLineItemOpp = opportunities.get(oli.OpportunityId);
     myDate = thisLineItemOpp.Contract_Start_Date__c;
      // thisLineItemOpp.CloseDate is the close date you're looking for
}

List<OpportunityLineItemSchedule> listOLIS = new List<OpportunityLineItemSchedule>();




for (OpportunityLineItem oli: Trigger.new){
Set<Id> setOpptyOLI = new set<Id>();
setOpptyOLI.add(oli.Id);
List<OpportunityLineItem> OpptyOlilst=[Select Product2.Family FROM OpportunityLineItem WHERE Id IN:setOpptyOLI ];

Decimal DiscountedPrice;



DiscountedPrice = oli.Discounted_Price__c;


if(oli.Payment_Terms__c == 'Monthly'){

for(OpportunityLineItem record : OpptyOlilst){
if(record.Product2.Family=='Services'){
    
    
for(Integer duration = (Integer)oli.Duration__c; duration > 0; duration--){

listOLIS.add(new OpportunityLineItemSchedule(OpportunityLineItemId = oli.Id, Revenue = DiscountedPrice*oli.Quantity/oli.Duration__c, ScheduleDate = myDate, Type = 'Revenue'));
myDate = myDate.addMonths(1);

}//end of loop
    
    }//end of inner IF
    
else {
for(Integer duration = (Integer)oli.Duration__c; duration > 0; duration--){

listOLIS.add(new OpportunityLineItemSchedule(OpportunityLineItemId = oli.Id, Revenue = DiscountedPrice*oli.Quantity, ScheduleDate = myDate, Type = 'Revenue'));
myDate = myDate.addMonths(1);

}//end of loop

}//end of else

}//end of OpptyOlilstForLoop

}//end of Monthly IF


    else if(oli.Payment_Terms__c == 'Quarterly'){
    
for(OpportunityLineItem record : OpptyOlilst){
if(record.Product2.Family=='Services'){
    
for(Integer duration = (Integer)oli.Duration__c/3; duration > 0; duration--){

listOLIS.add(new OpportunityLineItemSchedule(OpportunityLineItemId = oli.Id, Revenue = DiscountedPrice*oli.Quantity*3/oli.Duration__c, ScheduleDate = myDate, Type = 'Revenue'));
myDate = myDate.addMonths(3);

}//end of loop
    
    }//end of inner IF

else{
for(Integer duration = (Integer)oli.Duration__c/3; duration > 0; duration--){

listOLIS.add(new OpportunityLineItemSchedule(OpportunityLineItemId = oli.Id, Revenue = DiscountedPrice*oli.Quantity*3, ScheduleDate = myDate, Type = 'Revenue'));
myDate = myDate.addMonths(3);

}//end of loop

}//end of else

}//end of OpptyOlilstForLoop

}//end of Quarterly IF


    else if(oli.Payment_Terms__c == 'Annually'){
    
for(OpportunityLineItem record : OpptyOlilst){
if(record.Product2.Family=='Services'){
          
    
for(Integer duration = (Integer)oli.Duration__c/12; duration > 0; duration--){

listOLIS.add(new OpportunityLineItemSchedule(OpportunityLineItemId = oli.Id, Revenue = DiscountedPrice*oli.Quantity*12/oli.Duration__c, ScheduleDate = myDate, Type = 'Revenue'));
myDate = myDate.addYears(1);

}//end of loop
    
    }//end of inner IF

else{
for(Integer duration = (Integer)oli.Duration__c/12; duration > 0; duration--){

listOLIS.add(new OpportunityLineItemSchedule(OpportunityLineItemId = oli.Id, Revenue = DiscountedPrice*oli.Quantity*12, ScheduleDate = myDate, Type = 'Revenue'));
myDate = myDate.addYears(1);

}//end of loop

}//end of else

}//end of OpptyOlilstForLoop

}//end of Annually IF


    else if(oli.Payment_Terms__c == 'Up Front'){
    
for(OpportunityLineItem record : OpptyOlilst){
if(record.Product2.Family=='Services'){
    
    
listOLIS.add(new OpportunityLineItemSchedule(OpportunityLineItemId = oli.Id, Revenue = DiscountedPrice*oli.Quantity, ScheduleDate = myDate, Type = 'Revenue'));
  
    }//end of inner IF

else{
listOLIS.add(new OpportunityLineItemSchedule(OpportunityLineItemId = oli.Id, Revenue = DiscountedPrice*oli.Quantity*oli.Duration__c, ScheduleDate = myDate, Type = 'Revenue'));

}//end of else

}//end of OpptyOlilstForLoop

}//end of Up Front IF 


insert listOLIS;



}//end of isInsert

}//end of runOnce

}//end of checkRecursive

}//end of trigger

As you can see, my trigger now references the class I wrote to avoid recursion. 
The first time I tested the trigger, I got the following exception: 
System.DmlException: Insert failed. First exception on row 0; first error: UNABLE_TO_LOCK_ROW, unable to obtain exclusive access to this record: []
Class.testEditSchedule.testUpdateSchedule: line 48, column 1

The second time, I tested it by itself, and got this exception:
System.AssertException: Assertion Failed: Expected: true, Actual: false
Class.testEditSchedule.testUpdateSchedule: line 112, column 1

Lastly, here's the code for my test class:

@isTest (seeAllData=true)
private class testEditSchedule{

static testMethod void testUpdateSchedule() {
 
List<Account> aList = new List<Account>();    
Account a = new Account(
      Name = 'Test Account'
    );
    aList.add(a);
    
    insert aList;
    

List<Opportunity> oList = new List<Opportunity>();
Opportunity o = new Opportunity(
      Name = 'Test Opportunity', 
      StageName = 'Discovery', 
      CloseDate = Date.today(),
      Contract_Start_Date__c = Date.today(), 
      AccountId = a.Id
    );
    
    oList.add(o);
    insert oList;
    
        //Create Product
        Product2 prod = new Product2();
        prod.IsActive = true;
        prod.Name = 'Onboarding';
        prod.CanUseRevenueSchedule=true;
        insert prod;
        //Create PriceBook
        Pricebook2 pb = new Pricebook2();
        pb.Name = 'Test';
        pb.IsActive = true;
        insert pb;
        List<Pricebook2> PbList = [Select Id, Name, IsActive From Pricebook2 where IsStandard = true LIMIT 1];
    //Pricebook2 standardpb = [Select Id, Name, IsActive From Pricebook2 where IsStandard = true LIMIT 1];
        
            
        PricebookEntry standardpbe = new PricebookEntry();
        standardpbe.Pricebook2Id = PbList[0].Id;
        standardpbe.Product2Id = prod.Id;
        standardpbe.UnitPrice = 10000;
        standardpbe.IsActive = true;
        standardpbe.UseStandardPrice = false;
        insert standardpbe;
        
        PricebookEntry pbe = new PricebookEntry();
        pbe.Pricebook2Id = pb.Id;
        pbe.Product2Id = prod.Id;
        pbe.UnitPrice = 10000;
        pbe.IsActive = true;
        pbe.UseStandardPrice = false;
        insert pbe;
        
List<OpportunityLineItem> oliList = new List<OpportunityLineItem>(); 
List<OpportunityLineItem> oliAnnuallyList = new List<OpportunityLineItem>(); 
List<OpportunityLineItem> oliQuarterlyList = new List<OpportunityLineItem>(); 
List<OpportunityLineItem> oliMonthlyList = new List<OpportunityLineItem>(); 
       
OpportunityLineItem oliAnnually = new OpportunityLineItem(
        OpportunityId = o.Id,  
        PricebookEntryId = pbe.id,
        TotalPrice = 1,
        Quantity=2,
        Payment_Terms__c ='Annually',
        Duration__c = 12
        
    );
    OpportunityLineItem oliMonthly= new OpportunityLineItem(
        OpportunityId = o.Id,  
        PricebookEntryId = pbe.id,
        TotalPrice = 1,
        Quantity=2,
        Payment_Terms__c ='Monthly',
        Duration__c = 1
        
    );
    OpportunityLineItem oliQuarterly= new OpportunityLineItem(
        OpportunityId = o.Id,  
        PricebookEntryId = pbe.id,
        TotalPrice = 1,
        Quantity=2,
        Payment_Terms__c ='Quarterly',
        Duration__c = 3
        
    );
    OpportunityLineItem oli= new OpportunityLineItem(
        OpportunityId = o.Id,  
        PricebookEntryId = pbe.id,
        TotalPrice = 1,
        Quantity=2,
        Payment_Terms__c = 'Up Front',
        Duration__c = 12
        
    );
    oliAnnuallyList.add(oliAnnually);
    oliMonthlyList.add(oliMonthly);
    oliQuarterlyList.add(oliQuarterly);
    oliList.add(oli);
    insert oliList;
    insert oliAnnuallyList;
    insert oliQuarterlyList;
    insert oliMonthlyList;
    
    //Your trigger should have OpportunityLineItemSchedule logic then following asert will pass else it will fail
    List<OpportunityLineItem> lstOptyLi =[SELECT HasRevenueSchedule from OpportunityLineItem where Id =:oli.id];
    System.assertEquals(True, lstOptyLi[0].HasRevenueSchedule);
    List<OpportunityLineItem> lstOptyLi_2 =[SELECT HasRevenueSchedule from OpportunityLineItem where Id =:oliAnnually.id];
    System.assertEquals(True, lstOptyLi_2[0].HasRevenueSchedule);
    List<OpportunityLineItem> lstOptyLi_3 =[SELECT HasRevenueSchedule from OpportunityLineItem where Id =:oliQuarterly.id];
    System.assertEquals(True, lstOptyLi_3[0].HasRevenueSchedule);
    List<OpportunityLineItem> lstOptyLi_4 =[SELECT HasRevenueSchedule from OpportunityLineItem where Id =:oliMonthly.id];
    System.assertEquals(True, lstOptyLi_4[0].HasRevenueSchedule);
   
  }
    
}

I'm not sure why adding that little bit of code caused my test to stop working, but it thinks my trigger is messed up now. Can someone help me figure out how to fix it?

Thanks
Paul S.Paul S.
Travis - I believe this is because all of those list insertions occur within the same context, which means your recursive check is set to false after you insert oliList, and then it remains false as you're inserting the remaining lists.  I believe that if you break your test into four seperate test methods - one for each time period - the recursive check will reset between each and your test will pass.