[SalesForce] Inserting and Updating Record in Test Class

I'm an Apex novice – this is essentially the first new work I've done – and I'm trying to create a test class for a trigger that's intended to evaluate a Case record on insert or update, and set a boolean to true if any of the following criteria are met:

  • The case is being closed
  • The case is already closed and a select subset of fields is being edited (users must re-open the case to edit, but automated processes can update closed cases)

The trigger itself seems to behave as expected, but I'm getting tripped up on the test class. The first method seems OK, but I feel pretty certain that I'm not writing the other two (dealing with updates) properly. Here's the test class:

@isTest
class TestCaseNeedsArchive {
    static testMethod void testCaseBeingClosedAtCreate(){
        Case caseRecord = TestUtils.createTestCase();
        caseRecord.Status = 'Closed';
        insert caseRecord;
    }
    
    static testMethod void testCaseBeingClosed(){
        Case caseRecord = TestUtils.createTestCase();
        insert caseRecord;
        caseRecord.Status = 'Closed';
        update caseRecord;
    }
    
    static testMethod void testClosedCaseEditedNeedsArchive(){
        Case caseRecord = TestUtils.createTestCase();
        caseRecord.Status = 'Closed';
        insert caseRecord;
        caseRecord.Archive_Closed_Case_Needs_Update__c = false;
        caseRecord.Subject = 'Test Subject';
        update caseRecord;
    }
}

All the tests pass when I run them, but I can see from the code coverage that one line isn't being hit, which made me suspicious. It appears to me that the content after the insert in testCaseBeingClosed() and testClosedCaseEditedNeedsArchive() is not correct, but I'm not sure how to make it work. Any suggestions?

For reference, here's the trigger being tested. Note that I've trimmed the list of fields I'm evaluating down to just two, for ease of readability; there's really about 20 more fields in that else if:

trigger CaseNeedsArchive on Case (before insert,before update) {
    
    // Flag as needing to be archived if a case is closed during creation
    if(Trigger.isInsert){
        for(Case newCase : Trigger.new){
            if(newCase.IsClosed == true){
                newCase.Archive_Closed_Case_Needs_Update__c = true;
            }
        }
    }
    
    // Flag as needing to be archived if a case is closed OR if a closed case has edits to certain fields
    if(Trigger.isUpdate){
        for (Case newCase : Trigger.new) {
            Case oldCase = Trigger.oldMap.get(newCase.Id);
    
            if(newCase.IsClosed == true){
                if(oldCase.IsClosed == false){
                    newCase.Archive_Closed_Case_Needs_Update__c = true;
                }
                else if(newCase.ContactId != oldCase.ContactId || newCase.AccountId != oldCase.AccountId){
                    newCase.Archive_Closed_Case_Needs_Update__c = true;
                }
            }
        }
    }
}

I'd really appreciate any advice or thoughts you may have. Thanks!

Update 1 – Initial Attempt at Assertions

From the feedback in comments, I'm making an initial stab at adding some System.assertEquals statement, starting with the first test method (the one I think is working OK):

static testMethod void testCaseBeingClosedAtCreate(){
    Case caseRecord = TestUtils.createTestCase();
    caseRecord.Status = 'Closed';
    insert caseRecord;
 
    caseRecord = [SELECT Archive_Closed_Case_Needs_Update__c
                 FROM Case
                 WHERE Id = :caseRecord.Id];
    
    System.assertEquals(true, caseRecord.Archive_Closed_Case_Needs_Update__c);
}

The case runs and passes, and based on some system.debugs before and after the query, I can see that Archive_Closed_Case_Needs_Update__c was false before the lookup and true after. Does this seem like an appropriate format/implementation of an assertion?

In addition, it makes me question whether the problem is that I'm inserting and then updating without doing a lookup inbetween. Is the problem with mu update above just that it no longer has the Id value from the original create, thus it's updating to nothing? Or does that look like an appropriate use of an insert, followed by an update?

Update 2 – The Declarative Solution

Whelp, that detour through Apex was (technically) unnecessary. Turns out that one of the ways I'd attempted to implement via declarative routes was actually viable, I'd just used the wrong setting on the workflow rule (which had led me to believe I couldn't use an ISCHANGED formula). Here's how I'm solving for implementation purposes:

  1. Workflow rules check to see if case is being closed OR if closed case is being edited
  2. Field update sets the flag to true
  3. Process evaluates the changed record, finds the case with the set flag
  4. Process kicks off an autolaunched flow to do the work, and also unchecks the flag set in 2, above

That said, I'm still going to go through the exercise of getting the test case to work properly in my sandbox, just for the learning & experience. Also, the feedback in comments indicates that the test cases put in place by others are only half-baked and need assertions, so that gives me some additional work to do, as time allows.

Best Answer

Brian, here is a fleshed out version of your test class with some very basic asserts.

Basically, the pattern here is to do the work and then at the end query the data back out of the system and see if the results match the expectation.

@isTest
class TestCaseNeedsArchive {
    static testMethod void testCaseBeingClosedAtCreate(){
        Case caseRecord = TestUtils.createTestCase();

        // set the status as closed during insert
        caseRecord.Status = 'Closed';

        // reset governor limits for the actual test of the trigger
        Test.startTest();

        // insert the record which will be processed by the trigger
        insert caseRecord;

        // exit the testing context
        Test.stopTest();

        // check to ensure that the record that was just inserted
        // does have the Archive_Closed_Case_Needs_Update__c field set true
        List<Case> cases = [SELECT Id, Archive_Closed_Case_Needs_Update__c FROM Case WHERE Id = :caseRecord.Id];

        // there is only going to be one case in the list, but a loop does the job
        for (Case c : cases) {
            system.assertEquals(true, c.Archive_Closed_Case_Needs_Update__c, 'The value of Archive_Closed_Case_Needs_Update__c should be true.');
        }
    }

    static testMethod void testCaseBeingClosed(){
        Case caseRecord = TestUtils.createTestCase();

        // insert the record which will be processed by the trigger
        insert caseRecord;

        /* an assert can follow the insert similar to the one at the               */
        /* end of the testCaseNotClosedAtCreate() test, if you want to be thorough */

        // check to ensure that the record that was just inserted
        // does not have the Archive_Closed_Case_Needs_Update__c field set true
        List<Case> casesInserted = [SELECT Id, Archive_Closed_Case_Needs_Update__c FROM Case WHERE Id = :caseRecord.Id];

        // there is only going to be one case in the list, but a loop does the job
        for (Case c : casesInserted) {
            system.assertNotEquals(true, c.Archive_Closed_Case_Needs_Update__c, 'The value of Archive_Closed_Case_Needs_Update__c should not be true.');
        }

        // reset governor limits for the actual test of the update portion of the trigger
        Test.startTest();

        // change the status of the record and update it
        caseRecord.Status = 'Closed';
        update caseRecord;

        // exit the testing context
        Test.stopTest();

        // check to ensure that the record that was just updated
        // does have the Archive_Closed_Case_Needs_Update__c field set true
        List<Case> cases = [SELECT Id, Archive_Closed_Case_Needs_Update__c FROM Case WHERE Id = :caseRecord.Id];

        // there is only going to be one case in the list, but a loop does the job
        for (Case c : cases) {
            system.assertEquals(true, c.Archive_Closed_Case_Needs_Update__c, 'The value of Archive_Closed_Case_Needs_Update__c should be true.');
        }
    }

    // not sure what is being tested here from a logic perspective, but you can mimic the patterns of the other tests
    static testMethod void testClosedCaseEditedNeedsArchive(){
        Case caseRecord = TestUtils.createTestCase();
        caseRecord.Status = 'Closed';
        insert caseRecord;
        caseRecord.Archive_Closed_Case_Needs_Update__c = false;
        caseRecord.Subject = 'Test Subject';
        update caseRecord;
    }

    static testMethod void testCaseNotClosedAtCreate(){

        Case caseRecord = TestUtils.createTestCase();

        // reset governor limits for the actual test of the trigger
        Test.startTest();

        // insert the record which will be processed by the trigger
        insert caseRecord;

        // exit the testing context
        Test.stopTest();

        // check to ensure that the record that was just inserted
        // does not have the Archive_Closed_Case_Needs_Update__c field set true
        List<Case> cases = [SELECT Id, Archive_Closed_Case_Needs_Update__c FROM Case WHERE Id = :caseRecord.Id];

        // there is only going to be one case in the list, but a loop does the job
        for (Case c : cases) {
            system.assertNotEquals(true, c.Archive_Closed_Case_Needs_Update__c, 'The value of Archive_Closed_Case_Needs_Update__c should not be true.');
        }

    }
}
Related Topic