[SalesForce] System.NullPointerException: Attempt to de-reference a null object on After Insert trigger

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?

Best Answer

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);
Related Topic