Framework for buildings scalable, modular, and testable trigger implementations. This includes mocks for testing various trigger contexts as well as mechanisms to support proper unit and integration testing.
If there is little need to customize the framework, an unlocked package may be the best path. This code can be installed as an unlocked package here
For more information on unlocked packages are the right solution, refer to the unlocked package documentation
If you anticipate making changes to the framework to tailor it to your own needs, then the best path may be to download the source as a zip, extract the files, and move them into your project.
Navigate to Setup -> Custom Metadata -> SObject Trigger Setting -> Manage SObject Trigger Settings
Create or update a record (example): Label: Account Trigger Setting Name: Account Trigger Setting IsActive__c: True SObjectType__c: Account
Note To deactivate all trigger handlers for a particular sObject, then the IsActive Flag on the SObject Trigger Setting can be marked as false.
Navigate to Setup -> Custom Metadata -> Trigger Handler Setting -> Manage Trigger Handler Settings
Create or update a record (example): Label: Account Trigger Setting Name: Account Trigger Setting IsActive__c: True ClassName__c: MyTriggerHandler (the name of the class that this config maps to, see more below) SObjectTriggerSetting__c: Account Trigger Setting
Note Individual handlers should be activated/deactivated here
Create a new trigger file e.g. AccountTrigger.trigger and set it to fire for all trigger events
trigger AccountTrigger on Account (before insert, before update, before delete, after insert, after update, after delete, after undelete) {
TriggerDispatcherFactory.create(Account.getSObjectType()).dispatch();
}
Trigger handlers should extend the base virtual class TriggerHandler, and only need to override methods necessary to support business logic.
public class MyTriggerHandler extends TriggerHandler {
public override void doBeforeInsert() {
for (Account a : (List<Account>)this.ctx.getTriggerNew()) {
// do trigger logic
}
}
}
There may be several cases for which you want to intelligently disable triggers from code:
- Disable triggers for test data generation
- Disable triggers for ongoing migration or sync jobs
- Disable triggers for specific processes
While these use-cases may be few, and all risks and implications should be heavily considered, disabling triggers is easily done by invoking the shouldExecuteHandlers static method
TriggerDispatcher.shouldExecuteHandlers(false);// code beyond this will ignore handlers
// ... do some stuff
TriggerDispatcher.shouldExecuteHandlers(true); // code respects trigger logic again
There are several classes and methods that can be used to support testing efforts:
While mocking dependencies is typically the preferred approach, there may be times when you need to test with a DML operation. In those cases, it may be useful to test triggers in isolation. this can be done explicitly setting the handlers. SObjectTriggerSettingSelectorMock accepts a mapping that allows control over what handlers can be executed.
SObjectTriggerSettingSelectorMock selectorMock = new SObjectTriggerSettingSelectorMock(new Set<String> { 'MyHandlerName' });
TriggerDispatcherFactory.selector = (SObjectTriggerSettingSelector) System.Test.createStub(SObjectTriggerSettingSelector.class, selectorMock);
Often times, we will want to mock trigger contexts to execute true unit tests. Tests that rely on DML are inherently integration tests, because they test all automation (workflows, validation rules, process builders, etc.) that live on an object.
There are a set of classes -- 1 parent class with several inner classes -- which can be used to mock various trigger states. To use them in a test, just inject them into the handler:
List<Account> triggerNew = new List<Account> { new Account(Name='Test Account') };
TriggerContext ctx = (TriggerContext)System.Test.createStub(TriggerContext.class, new TriggerContextMocks.BeforeInsertMock(triggerNew));
ITriggerHandler handler = new MyTriggerHandler();
handler.setTriggerContext(ctx); // now you have access to the trigger context which will be passed in via the dispatchr