6

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.

  • 2
    You should first try adding some system.assertEquals statements into your test code to validate your assumptions about the data. Heres another question to help out: salesforce.stackexchange.com/questions/55386/… – techbusinessman Aug 5 '15 at 20:26
  • An earlier draft of the test case had some system.assert() entries, but I wasn't sure if I was entering them correctly. I'll review that question you linked to, and the documents it, in turn, references. – Brian Pitts Aug 5 '15 at 20:37
  • 1
    No assertions mean your tests are practically worthless. It is very difficult to interpret what you are actually testing. – Adrian Larson Aug 5 '15 at 20:38
  • The way I was confirming that it was not functioning as expected was by running the code from within each test method via execute anonymous. The case created from the first method was as I expected, but the case created from the second was not flagged (the third one worked OK, but I suspect that was because I set status = closed before the insert, which is the same as method 1). The results from method 2 vs. the black box testing that works as-expected leads me to believe I'm not doing the update correctly. – Brian Pitts Aug 5 '15 at 20:40
  • 1
    @BrianPitts is there any particular reason you are implementing this with a trigger and code as opposed to the much easier solution of a workflow rule and a field update action? – Mark Pond Aug 5 '15 at 22:40
4

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.');
        }

    }
}
  • Thank you so much! I really appreciate all the comments - it's really helpful in learning for both this and future implementations. Thanks!!! – Brian Pitts Aug 6 '15 at 19:58

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.