Test Apex Triggers
Learning Objectives
After completing this unit, you’ll be able to:
- Write a test for a trigger that fires on a single record operation.
- Execute all test methods in a class.
Prerequisites
Let’s test a trigger that we worked with in the Apex Triggers module. If an account record has related opportunities, the AccountDeletion trigger prevents the record’s deletion.
If you haven’t yet added the AccountDeletion trigger, follow these steps.
- In Web Console, press Ctrl+Shift+P (Windows) or ⌘+Shift+P (macOS) to open the Command Palette.
- Enter
SFDX: Create Apex Triggerand press Enter.
- For the trigger name, enter
AccountDeletion, and then press Enter.
- For the sObject, select Account, and then press Enter.
- For the trigger event, select before delete, and then press Enter.
- For the output directory, select force-app/main/default/triggers, and then press Enter.
- Replace the default trigger body with this example.
trigger AccountDeletion on Account (before delete) {
// Prevent the deletion of accounts if they have related opportunities.
for(Account a : [SELECT Id FROM Account
WHERE Id IN (SELECT AccountId FROM Opportunity) AND
Id IN :Trigger.old WITH USER_MODE]) {
Trigger.oldMap.get(a.Id).addError('Cannot delete account with related opportunities.');
}
}If you added the AccountDeletion trigger in a previous unit but disabled it so that the system could check your challenge, re-enable it.
- From Setup, search for
Apex Triggers.
- On the Apex Triggers page, click Edit next to the
AccountDeletiontrigger.
- Select Is Active.
- Click Save.
If your org contains triggers from a previous unit called AddRelatedRecord, CalloutTrigger, or HelloWorldTrigger, disable them. For example, to disable the AddRelatedRecord trigger:
- From Setup, search for
Apex Triggers.
- On the Apex Triggers page, click Edit next to the
AddRelatedRecordtrigger.
- Deselect Is Active.
- Click Save.
To disable the HelloWorldTrigger and CalloutTrigger triggers, repeat the previous steps.
Add and Run a Unit Test
Before deploying the trigger, write unit tests to perform the actions that fire the trigger and verify expected results.
First, let’s start by adding a test method. This test method verifies what the trigger is designed to do (the positive case): preventing an account from being deleted if it has related opportunities.
- In Web Console, press Ctrl+Shift+P (Windows) or ⌘+Shift+P (macOS) to open the Command Palette.
- Enter
SFDX: Create Apex Classand press Enter.
- For the template type, select DefaultApexClass, and then press Enter.
- For the class name, enter
TestAccountDeletion, and then press Enter.
- For the output directory, select force-app/main/default/classes, and then press Enter.
- Replace the default class body with the following.
@IsTest
private with sharing class TestAccountDeletion {
@IsTest
static void TestDeleteAccountWithOneOpportunity() {
// Test data setup
// Create an account with an opportunity, and then try to delete it
Account acct = new Account(Name='Test Account');
insert as user acct;
Opportunity opp = new Opportunity(
Name=acct.Name + ' Opportunity',
StageName='Prospecting',
CloseDate=System.today().addMonths(1),
AccountId=acct.Id);
insert as user opp;
// Perform test
Test.startTest();
Database.DeleteResult result = Database.delete(acct, false, AccessLevel.USER_MODE);
Test.stopTest();
// Verify
// In this case the deletion should have been stopped by the trigger,
// so verify that we got back an error.
Assert.isFalse(result.isSuccess());
Assert.isTrue(result.getErrors().size() > 0);
Assert.areEqual('Cannot delete account with related opportunities.',
result.getErrors()[0].getMessage());
}
}The test method first sets up a test account with an opportunity. Next, it deletes the test account, which fires the AccountDeletion trigger. The test method verifies that the trigger prevented the deletion of the test account by checking the return value of the Database.delete() call. The return value is a Database.DeleteResult object that contains information about the delete operation. The test method verifies that the deletion was not successful and verifies the error message obtained.
- Save the test class.
- In Web Console, click
in the Activity Bar. The Testing side panel opens.
- In the Testing search bar, enter
TestAccountDeletion.
- To run all test methods in the class, hover over the TestAccountDeletion line and click
.
The TestAccountDeletion test class contains only one test method, which tests for a single account record. Also, this test is for the positive case. Always test for more scenarios to ensure that the trigger works in all cases, including deleting an account without opportunities and bulk account deletions.
Test data is set up inside the test method, which can be time-consuming as you add more test methods. If you have many test methods, put test-data creation in a test utility class and call the utility class from multiple test methods. The next unit shows you how to take advantage of a test utility class and add more test methods.
Tell Me More
The test method contains the Test.startTest() and Test.stopTest() method pair, which delimits a block of code that gets a fresh set of governor limits. In this test, test-data setup uses two DML statements before the test is performed. To test that Apex code runs within governor limits, isolate data setup’s limit usage from your test’s. To isolate the data setup process’s limit usage, enclose the test call within the Test.startTest() and Test.stopTest() block. Also use this test block when testing asynchronous Apex. For more information, see Using Limits, startTest, and stopTest.
Resources
Get Ready for the Hands-on Challenge
In order to complete the hands-on challenge for this unit, you’ll need to create an Apex trigger on the Contact object named RestrictContactByName with the code below:
trigger RestrictContactByName on Contact (before insert, before update) {
//check contacts prior to insert or update for invalid data
for(Contact c : Trigger.New) {
if(c.LastName == 'INVALIDNAME') {
//invalidname is invalid
c.AddError('The Last Name "'+c.LastName+'" is not allowed for DML');
}
}
}