1

I've written an Apex class to update a field based on valued in a custom setting. However, I get the following error when I run the test class:

System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, ProjectHoursTrigger: execution of AfterInsert

caused by: System.NullPointerException: Attempt to de-reference a null object

Class.UpdatePayrollPeriod.updatePayrollPeriod: line 5, column 1 Trigger.ProjectHoursTrigger: line 8, column 1: []

Here is the trigger:

trigger ProjectHoursTrigger on Project_Hours__c (after insert,  after update) {

    List<Project_Hours__c> projectHoursList = new List<Project_Hours__c>();
    for (Project_Hours__c p : Trigger.new){
    projectHoursList.add(p);
    }
    if(projectHoursList.size()>0){
        UpdatePayrollPeriod.updatePayrollPeriod(projectHoursList); 
        update projectHoursList;
    }
}

Here's the class:

public class UpdatePayrollPeriod {
public static void updatePayrollPeriod(List<Project_Hours__c> projectHoursList){
    for (Project_Hours__c ph : projectHoursList){
        First_Pay_Period__c p = First_Pay_Period__c.getValues(string.valueOf(ph.Year__c));
        ph.Payroll_Period2__c = ((p.End_of_First_Pay_Period__c.daysBetween(ph.Date__c))
                                /14)+1;
    }
}
}

And here's the test class:

@isTest
private class TestUpdatePayrollPeriod {
static testMethod void TestPayrollPeriod() {
    
    // Organization (Account)
    Account testOrg             = new Account();
    testOrg.RecordTypeId        = Schema.SObjectType.Account.getRecordTypeInfosByName().get('Organization').getRecordTypeId();
    testOrg.Name                = 'TestAccount';
    insert testOrg;
    
    //Contact
    Contact testContact         = new Contact();
    testContact.RecordTypeId    = Schema.SObjectType.Contact.getRecordTypeInfosByName().get('Member Contact').getRecordTypeId();
    testContact.LastName        = 'TestContact';
    testContact.AccountId       = testOrg.Id;
    insert testContact;
    
    // Revenue (Opportunity)
    Opportunity testRev         = new Opportunity();
    testRev.RecordTypeId        = Schema.SObjectType.Opportunity.getRecordTypeInfosByName().get('Project').getRecordTypeId();
    testRev.Name                = 'Test Revenue';
    testRev.AccountId           = testOrg.Id;
    testRev.CloseDate           = Date.today();
    testRev.StageName           = 'Closed Won';
    insert testRev;
    
    // Project
    Projects__c testProj        = new Projects__c();
    testProj.Name               = 'Test Project';
    testProj.Revenue__c         = testRev.Id;
    testProj.Project_Code__c    = 'Test Project';
    insert testProj;
    
    // Term
    Term__c testTerm            = new Term__c();
    testTerm.Name               = 'Test Term';
    testTerm.Contact__c         = testContact.Id;
    insert testTerm;
    
    // Project Hours
    Project_Hours__c testHours  = new Project_Hours__c();
    testHours.Project_new__c    = testProj.Id;
    testHours.Contact_Term__c   = testTerm.Id;
    testHours.Year__c           = 15;
    testHours.Date__c           = Date.valueOf('2015-01-24 04:00:00');
    insert testHours;
    
    System.assertEquals(testHours.Payroll_Period2__c, 2);
}
}

Any suggestions on what could be causing this error?

3 Answers 3

3

Your trigger is wrong. It introduces an infinite loop, so your code will crash. Instead, use before insert/before update. Also, there's no need to copy the list. Your entire trigger can be written as:

trigger ProjectHoursTrigger on Project_Hours__c (before insert, before update) {
    UpdatePayrollPeriod.updatePayrollPeriod(Trigger.new); 
}

As for the actual error, you should be checking if the fields are null first:

public class UpdatePayrollPeriod {
    public static void updatePayrollPeriod(List<Project_Hours__c> projectHoursList) {
        for (Project_Hours__c ph : projectHoursList){
            if(ph.Year__c != null && ph.Date__c != null) {
                First_Pay_Period__c p = First_Pay_Period__c.getValues(string.valueOf(ph.Year__c));
                if(p != null && p.End_of_First_Pay_Period__c != null) {
                    ph.Payroll_Period2__c = ((p.End_of_First_Pay_Period__c.daysBetween(ph.Date__c))/14)+1;
                }
            }
        }
    }
}

For code coverage purposes, make sure your unit test is initializing the Date__c field, and the Year__c field, assuming it's not a formula.

Finally, it looks like you're using SeeAllData=false (the default test isolation mode), so you should also insert a new First_Pay_Period__c record before inserting the Project_Hours__c record.

Edit: The "p" variable in your class was not, not the field itself. This is because, as the last paragraph notes, you did not insert a First_Pay_Period__c record.

Edit 2: After a DML statement, you need to query the records back from the database to see what the final saved values are:

testHours = [SELECT PayRoll_Period2__c FROM Project_Hours__c WHERE Id = :testHours.Id];
System.assertEquals(testHours.Payroll_Period2__c, 2);
6
  • I noticed you took out the DML statement, so the field update doesn't happen. When I added it to the end of the class, I get the following new error: System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, ProjectHoursTrigger: execution of BeforeInsert caused by: System.SObjectException: DML statement cannot operate on trigger.new or trigger.old Class.UpdatePayrollPeriod.updatePayrollPeriod: line 11, column 1 Trigger.ProjectHoursTrigger: line 2, column 1: [] Commented Jun 16, 2016 at 19:18
  • @TheDevAdmin as i said, you're missing data. When you modify Trigger.new in before insert/update, they will be saved to the database. However, you need to insert a First_Pay_Period__c record.
    – sfdcfox
    Commented Jun 16, 2016 at 19:23
  • I added First_Pay_Period__c to the test class so it is inserted before Project_Hours__c, but still get the error above. // First Pay Period custom setting First_Pay_Period__c testPeriod = new First_Pay_Period__c(); testPeriod.Name = '15'; testPeriod.Year__c = 15; testPeriod.End_of_First_Pay_Period__c = Date.newInstance(2015, 01, 10); insert testPeriod;' Commented Jun 16, 2016 at 19:56
  • @TheDevAdmin No, I meant you don't need the DML statement. Start from what I gave you, and keep your test class the way it is after you added the First Pay Period record.
    – sfdcfox
    Commented Jun 16, 2016 at 20:00
  • When I do that, the field I'm trying to update, Payroll_Period2__c on Project_Hours__c never gets updated. Don't I need a DML statement to update the Project_Hours__c in order for the field to get updated? Commented Jun 16, 2016 at 20:17
0

I think End_of_First_Pay_Period__c is null.

If you can assign values to it from your test class it will work.

0

Make a new Class CheckRecursive with the following code:

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

and edit your trigger like this:

trigger ProjectHoursTrigger on Project_Hours__c (after insert,  after update) {
 if(checkRecursive.runOnce())
{
List<Project_Hours__c> projectHoursList = new List<Project_Hours__c>();
for (Project_Hours__c p : Trigger.new){
projectHoursList.add(p);
}
if(projectHoursList.size()>0){
    UpdatePayrollPeriod.updatePayrollPeriod(projectHoursList); 
    update projectHoursList;
}
}
}

This will prevent the trigger from running infinitely after each update.

1
  • N.B. this recursion control technique fails if DML is done from a VF controller, execute anonymous, or when any other APEX code inserts/updates > 200 records in a single statement. Records 201+ will never flow through the trigger
    – cropredy
    Commented Jun 17, 2016 at 0:39

You must log in to answer this question.

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