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

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);
| improve this answer | |
  • 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: [] – TheDevAdmin Jun 16 '16 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 Jun 16 '16 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;' – TheDevAdmin Jun 16 '16 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 Jun 16 '16 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? – TheDevAdmin Jun 16 '16 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.

| improve this answer | |
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.

| improve this answer | |
  • 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 Jun 17 '16 at 0:39

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.