function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Mark_KaprielianMark_Kaprielian 

How to update a single field as part of an Apex Trigger test

Hello All
I'm new to Apex.  I have successfully created a Case Trigger and a Class which I call from it.   I have 100% coverage of my Class methods.   I now need to write test coverage for my Case Trigger code.  Classes and Methods seem straight forward to me as it's pure calls.  For the Case Trigger code, I need to do things to the system to have it invoke things.  This I do not know how to do despite trying to find examples.

I believe a simple example would help me understand the technique and in fact would be the solution I need for this my first attempt at Trigger testing.

My Case trigger is to designed to take action when the Case  Status field changes.
My trigger test needs to change the status to traverse the different actions that are taken based on the new status.
  1. Do I need to create a new case record for each test then change the status before I loose the scope of the test?
    • Note there are several required fields I would need to satisfy to create a new case record
  2. If yes to the above how can I push the creation of the record into a sub-class and then reference that case record while still in the scope of the test classes?

   Mark
 
Best Answer chosen by Mark_Kaprielian
Andrew GAndrew G
Hi Mark
Depending on the structure of your trigger, it should be very simple to achieve code coverage.  Everyone is slightly different in how they code, but I choose to keep my triggers as simple as possible.  Example:
trigger CaseTrigger on Case (before insert, before update) {

	CaseTriggerHandler objHandler = new CaseTriggerHandler();
	if ( trigger.isBefore ) {
		if ( trigger.isInsert ) {
			objHandler.handleBeforeInsert(Trigger.New);
		}
		if ( trigger.isUpdate )  {
			objHandler.handleBeforeUpdate(Trigger.Old, Trigger.New, Trigger.oldMap, Trigger.newMap);
		}
	}
}
As seen, I have minimal code in my trigger.  Similarly, my Handler method do the minimal required and call other methods to keep the reading of the Handler as simple as possible.
public class CaseTriggerHandler {
    
    public void handleBeforeInsert(List<Case> newCases) {
        System.debug ('DEBUG: CaseTriggerHandler: handle Before Insert');
        updateNewDescription(newCases);
    }

    public void handleBeforeUpdate(List<Case> oldCases, List<Case> newCases, Map<Id,Case> oldValues, Map<Id,Case> newValues) {
        System.debug ('DEBUG: CaseTriggerHandler: handle Before Update');
        updateOldDescription(oldValues, newCases);
    }

    private static void updateNewDescription(List<Case> newCases) {
        for (Case cs : newCases ) {
            if (cs.Status == 'New') {
                cs.Description = 'New Case';
            }
        }
    }

    private static void updateOldDescription(Map<Id,Case> oldValues, List<Case> newCases) {
        for (Case cs : newCases ) {
            if  (cs.Status == 'Closed' && oldValues.get(cs.Id).Status == 'New') {
                 cs.Description = 'Closed from new';
            }
            if  (cs.Status == 'In progress' && oldValues.get(cs.Id).Status == 'Closed') {
                 cs.Description = 'Reopened';
            }
        }   
    }
}

So, now the question on your testing.
If you have common Test data that is relevant to the entire testing code, then use of @testSetup is recommended.  When data is setup this way, regardless what you do during each test method, the data will "reset" for the next test method.  So, if the test Case record is created with a Status of "new" and the first test method changes that to "old", when the test data is queried in the next test method, it will be back to "new".
Example of using @TestSetup.
Note that I am using Test Helper class to create my test data, but the test data can also be explicitly created in the @testSetup section.
 
@isTest
private class CaseTriggerHandlerTest {

@testSetup static void setupTestRecords() {
		//Create common test records
		List<Account> testaccs = TestClassHelper.createTestAccounts(1);
		Insert testaccs;
		//Create Service Case
		List<Case> testCases = TestClassHelper.createTestCases(1, testaccs[0].Id);
		Insert testCases;
	}
	
	@isTest static void test_description_on_insert() {
		// Implement test code
		//get the test records that were created
		List<Account> accs = new List<Account>([SELECT Id FROM Account WHERE Name LIKE 'Test Account%']);
		List<Case> testCases = new List<Case> ([SELECT Id, Status, Description FROM Case WHERE AccountId = :accs[0].Id]);

		for (Case cs : testCases ) {
			System.assertEquals('New Case',cs.Description);
		}
	}
	
	@isTest static void test_description_on_update_1() {
		// Implement test code
		//get the test records that were created
		List<Account> accs = new List<Account>([SELECT Id FROM Account WHERE Name LIKE 'Test Account%']);
		List<Case> testCases = new List<Case> ([SELECT Id, Status, Description FROM Case WHERE AccountId = :accs[0].Id]);

		for (Case cs : testCases ) {
			cs.Status = 'Closed';
		}
		update testCases;

		testCases = new List<Case> ([SELECT Id, Status, Description FROM Case WHERE AccountId = :accs[0].Id]);
		for (Case cs : testCases ) {
			System.assertEquals('Closed from new',cs.Description);
		}

		for (Case cs : testCases ) {
			cs.Status = 'In progress';
		}
		update testCases;

		testCases = new List<Case> ([SELECT Id, Status, Description FROM Case WHERE AccountId = :accs[0].Id]);
		for (Case cs : testCases ) {
			System.assertEquals('Reopened',cs.Description);
		}
	}
}
Now, if we look at the testing.

I am running 2 test methods.  One for the Insert , One for the Update.  Depending on the complexity of the trigger, I generally run one test method for each method and for each branch of the method.  So if there is an IF .... ELSE .... branch, i would have a test for each branch 
so:
if (case.status == 'NEW' ) {
    //do NEW stuff 
} else {
  // do OTHER stuff
}
The test methods would look like 
@isTest static void test_and_validate_NEW_stuff () {
    //implement test code
}

@isTest static void test_and_validate_OTHER_stuff () {
    //implement test code
}

for completeness, here is the testclass helper
 
@IsTest
public class TestClassHelper {

	//Create Standard Accounts
    public static List<Account> createTestAccounts(integer numberOfAccs) {
        List<Account> accList = new List<Account>();
        for(Integer i= 0; i<numberOfAccs; i++){
            Account acc = new Account(Name='Test Account'+ string.valueOf(i + 1));
            accList.add(acc);
        }        
        return accList;
    }

    //Create Cases
    public static List<Case> createTestCases(integer numberofCases, Id accId){
        List<Case> caseList = new List<Case>();
        for(Integer i=0; i<numberofCases; i++){
            Case cse = new Case(AccountId = accId,
                                Status = 'New'
                                );
            caseList.add(cse);
        }
        return caseList;
    }
}
Note that I return lists of records from my test class helper, that way they are generic and I can tweak them if need for each actual test before the insert.  Example:
List<Case> testCases = TestClassHelper.createTestCases(1, testaccs[0].Id);
	for (Case cs : testCases ) {
		cs.Status = 'In progress';
	}
	Insert testCases;


Hope the above provides some help and clarity

Regards
​​​​​​​Andrew
 

All Answers

Andrew GAndrew G
Hi Mark
Depending on the structure of your trigger, it should be very simple to achieve code coverage.  Everyone is slightly different in how they code, but I choose to keep my triggers as simple as possible.  Example:
trigger CaseTrigger on Case (before insert, before update) {

	CaseTriggerHandler objHandler = new CaseTriggerHandler();
	if ( trigger.isBefore ) {
		if ( trigger.isInsert ) {
			objHandler.handleBeforeInsert(Trigger.New);
		}
		if ( trigger.isUpdate )  {
			objHandler.handleBeforeUpdate(Trigger.Old, Trigger.New, Trigger.oldMap, Trigger.newMap);
		}
	}
}
As seen, I have minimal code in my trigger.  Similarly, my Handler method do the minimal required and call other methods to keep the reading of the Handler as simple as possible.
public class CaseTriggerHandler {
    
    public void handleBeforeInsert(List<Case> newCases) {
        System.debug ('DEBUG: CaseTriggerHandler: handle Before Insert');
        updateNewDescription(newCases);
    }

    public void handleBeforeUpdate(List<Case> oldCases, List<Case> newCases, Map<Id,Case> oldValues, Map<Id,Case> newValues) {
        System.debug ('DEBUG: CaseTriggerHandler: handle Before Update');
        updateOldDescription(oldValues, newCases);
    }

    private static void updateNewDescription(List<Case> newCases) {
        for (Case cs : newCases ) {
            if (cs.Status == 'New') {
                cs.Description = 'New Case';
            }
        }
    }

    private static void updateOldDescription(Map<Id,Case> oldValues, List<Case> newCases) {
        for (Case cs : newCases ) {
            if  (cs.Status == 'Closed' && oldValues.get(cs.Id).Status == 'New') {
                 cs.Description = 'Closed from new';
            }
            if  (cs.Status == 'In progress' && oldValues.get(cs.Id).Status == 'Closed') {
                 cs.Description = 'Reopened';
            }
        }   
    }
}

So, now the question on your testing.
If you have common Test data that is relevant to the entire testing code, then use of @testSetup is recommended.  When data is setup this way, regardless what you do during each test method, the data will "reset" for the next test method.  So, if the test Case record is created with a Status of "new" and the first test method changes that to "old", when the test data is queried in the next test method, it will be back to "new".
Example of using @TestSetup.
Note that I am using Test Helper class to create my test data, but the test data can also be explicitly created in the @testSetup section.
 
@isTest
private class CaseTriggerHandlerTest {

@testSetup static void setupTestRecords() {
		//Create common test records
		List<Account> testaccs = TestClassHelper.createTestAccounts(1);
		Insert testaccs;
		//Create Service Case
		List<Case> testCases = TestClassHelper.createTestCases(1, testaccs[0].Id);
		Insert testCases;
	}
	
	@isTest static void test_description_on_insert() {
		// Implement test code
		//get the test records that were created
		List<Account> accs = new List<Account>([SELECT Id FROM Account WHERE Name LIKE 'Test Account%']);
		List<Case> testCases = new List<Case> ([SELECT Id, Status, Description FROM Case WHERE AccountId = :accs[0].Id]);

		for (Case cs : testCases ) {
			System.assertEquals('New Case',cs.Description);
		}
	}
	
	@isTest static void test_description_on_update_1() {
		// Implement test code
		//get the test records that were created
		List<Account> accs = new List<Account>([SELECT Id FROM Account WHERE Name LIKE 'Test Account%']);
		List<Case> testCases = new List<Case> ([SELECT Id, Status, Description FROM Case WHERE AccountId = :accs[0].Id]);

		for (Case cs : testCases ) {
			cs.Status = 'Closed';
		}
		update testCases;

		testCases = new List<Case> ([SELECT Id, Status, Description FROM Case WHERE AccountId = :accs[0].Id]);
		for (Case cs : testCases ) {
			System.assertEquals('Closed from new',cs.Description);
		}

		for (Case cs : testCases ) {
			cs.Status = 'In progress';
		}
		update testCases;

		testCases = new List<Case> ([SELECT Id, Status, Description FROM Case WHERE AccountId = :accs[0].Id]);
		for (Case cs : testCases ) {
			System.assertEquals('Reopened',cs.Description);
		}
	}
}
Now, if we look at the testing.

I am running 2 test methods.  One for the Insert , One for the Update.  Depending on the complexity of the trigger, I generally run one test method for each method and for each branch of the method.  So if there is an IF .... ELSE .... branch, i would have a test for each branch 
so:
if (case.status == 'NEW' ) {
    //do NEW stuff 
} else {
  // do OTHER stuff
}
The test methods would look like 
@isTest static void test_and_validate_NEW_stuff () {
    //implement test code
}

@isTest static void test_and_validate_OTHER_stuff () {
    //implement test code
}

for completeness, here is the testclass helper
 
@IsTest
public class TestClassHelper {

	//Create Standard Accounts
    public static List<Account> createTestAccounts(integer numberOfAccs) {
        List<Account> accList = new List<Account>();
        for(Integer i= 0; i<numberOfAccs; i++){
            Account acc = new Account(Name='Test Account'+ string.valueOf(i + 1));
            accList.add(acc);
        }        
        return accList;
    }

    //Create Cases
    public static List<Case> createTestCases(integer numberofCases, Id accId){
        List<Case> caseList = new List<Case>();
        for(Integer i=0; i<numberofCases; i++){
            Case cse = new Case(AccountId = accId,
                                Status = 'New'
                                );
            caseList.add(cse);
        }
        return caseList;
    }
}
Note that I return lists of records from my test class helper, that way they are generic and I can tweak them if need for each actual test before the insert.  Example:
List<Case> testCases = TestClassHelper.createTestCases(1, testaccs[0].Id);
	for (Case cs : testCases ) {
		cs.Status = 'In progress';
	}
	Insert testCases;


Hope the above provides some help and clarity

Regards
​​​​​​​Andrew
 
This was selected as the best answer
Mark_KaprielianMark_Kaprielian
Hello Andrew

It will take me some time to work through your solution but it looks like I should be able to both learn and execute from the work you did to answer my questions.    Thank you for putting it all in perspective.  

Great work, I really appreciate you taking the time to provide this material.

   Mark