Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save VivekMChawla/ea03fab88339b43c266b to your computer and use it in GitHub Desktop.
Save VivekMChawla/ea03fab88339b43c266b to your computer and use it in GitHub Desktop.
This gist contains the Simple Trigger Pattern for Salesforce (STP), created by Vivek M. Chawla (@VivekMChawla).

##Introduction

This gist contains the Simple Trigger Pattern for Salesforce (STP). This pattern was developed by Salesforce MVP Vivek M. Chawla (@VivekMChawla), a Salesforce Certified App Builder, Platform Developer I and II, and Advanced Administrator.

The Simple Trigger Pattern can be used to quick-start development of new triggers. It encapsulates several best-practices, including "One Trigger per Object", "No Business Logic in Triggers", and "Recursion Prevention Using Static Variables". The STP also facilitates adoption of the architectural best practice of Separation of Concerns.

NOTE: If you are looking for the Git Repository for the Simple Trigger Pattern, you can find it here: bit.ly/SimpleTriggerPattern

##Contents

The following files are contained by this collection:

  • 01 - License.md
    • Copyright and license information
    • All code and documentation are distributed under MIT License
    • If you find this pattern useful, let me know @VivekMChawla
  • 02 - TriggerHandler.class.java
    • Apex virtual class
    • Core component of the Simple Trigger Pattern
    • Must be deployed to your org before implementing STP
    • Does not require customization before deployment
    • All trigger handlers implementing STP must extend this class
  • 03 - TriggerHandlerTest.class.java
    • Apex test class
    • Must be deployed to your org before implementing STP
    • Does not require customization before deployment
    • Provides 97% code coverage of the TriggerHandler virtual class
  • 04 - SampleObjectTrigger.trigger.java
    • Apex trigger template
    • Requires customization before deploying to your org
  • 05 - SampleObjectTriggerHandler.class.java
    • Apex class template
    • Requires customization before deploying to your org
  • 06 - SampleObjectTriggerHandlerTest.class.java
    • Apex test class template
    • Requires customization before deploying to your org

Copyright (c) 2015 Vivek M. Chawla (Vivek.M.Chawla@gmail.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/**
* ─────────────────────────────────────────────────────────────────────────────────────────────────┐
* This class is a core component of the Simplified Trigger Pattern for Salesforce (STP).
*
* All trigger handler classes that implement the Simple Trigger Pattern must extend this class.
* Please note that under normal circumstances, this class should not be modified.
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* Copyright (c) 2015 Vivek M. Chawla (@VivekMChawla)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* @author Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @modifiedBy Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @maintainedBy Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @version 1.1
* @created 2014-03-12
* @modified 2015-04-05
* @systemLayer Invocation
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* @changes
* v1.0 Vivek.M.Chawla@gmail.com
* 2014-03-12 Initial implementation.
*
* v1.1 Vivek.M.Chawla@gmail.com
* 2015-04-05 Added additional comments explaining how the code in this class works. Changed
* the HandlerException class from public to private. Simplified the "recursion
* check" logic and added @testVisible annotations to each one. Migrated from a CC
* license to the MIT license.
* ─────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
public virtual class TriggerHandler {
//───────────────────────────────────────────────────────────────────────────┐
// Initialize static booleans to track if this is the first time a specific
// Trigger Action has been called within the current Execution Context.
//───────────────────────────────────────────────────────────────────────────┘
public static boolean isBeforeInsertFirstRun = true;
public static boolean isBeforeUpdateFirstRun = true;
public static boolean isBeforeDeleteFirstRun = true;
public static boolean isAfterInsertFirstRun = true;
public static boolean isAfterUpdateFirstRun = true;
public static boolean isAfterDeleteFirstRun = true;
public static boolean isAfterUndeleteFirstRun = true;
//───────────────────────────────────────────────────────────────────────────┐
// If the trigger was invoked by a DML operation on more than 200 records,
// multiple instances of the handler class will be created within the same
// execution context. This attribute tracks the batch size each instance
// works on.
//───────────────────────────────────────────────────────────────────────────┘
protected final integer batchSize;
//───────────────────────────────────────────────────────────────────────────┐
// Declare an inner class that extends Exception. This will allow us to throw
// a custom Exception from within this Handler Class. If presented to a user,
// or inspected in a debug log, this exception will appear as an exception of
// type "TriggerHandler.HandlerException".
//───────────────────────────────────────────────────────────────────────────┘
private class HandlerException extends Exception {}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* Constructor
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
public TriggerHandler() {
//─────────────────────────────────────────────────────────────────────────┐
// Ensure that this handler class is being instantiated by a Trigger. If it
// is not, then we must kill execution and throw an Exception. The only
// ...ahem..."exception" to this rule is if this handler class is being
// instantiated by a test method.
//─────────────────────────────────────────────────────────────────────────┘
if (Trigger.isExecuting != true && Test.isRunningTest() == false) {
throw new HandlerException('This class may only be instantiated within a Trigger-based '
+'Execution Context.');
}
//─────────────────────────────────────────────────────────────────────────┐
// Initialize the batchSize instance variable with the value from the
// Trigger.size context variable. This allows us to track the number of
// records in the trigger batch at the time this class was instantiated.
//─────────────────────────────────────────────────────────────────────────┘
batchSize = Trigger.size;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* Checks if the BEFORE INSERT static flag has been tripped, and trips the flag if it has not.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @return boolean Returns TRUE if the BEFORE INSERT trigger has already run, FALSE if not.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
@testVisible
protected boolean beforeInsertHasRun() {
if (isBeforeInsertFirstRun) {
return isBeforeInsertFirstRun = false;
}
return true;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* Checks if the BEFORE UPDATE static flag has been tripped, and trips the flag if it has not.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @return boolean Returns TRUE if the BEFORE UPDATE trigger has already run, FALSE if not.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
@testVisible
protected boolean beforeUpdateHasRun() {
if (isBeforeUpdateFirstRun) {
return isBeforeUpdateFirstRun = false;
}
return true;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* Checks if the BEFORE DELETE static flag has been tripped, and trips the flag if it has not.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @return boolean Returns TRUE if the BEFORE DELETE trigger has already run, FALSE if not.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
@testVisible
protected boolean beforeDeleteHasRun() {
if (isBeforeDeleteFirstRun) {
return isBeforeDeleteFirstRun = false;
}
return true;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* Checks if the AFTER INSERT static flag has been tripped, and trips the flag if it has not.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @return boolean Returns TRUE if the AFTER INSERT trigger has already run, FALSE if not.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
@testVisible
protected boolean afterInsertHasRun() {
if (isAfterInsertFirstRun) {
return isAfterInsertFirstRun = false;
}
return true;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* Checks if the AFTER UPDATE static flag has been tripped, and trips the flag if it has not.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @return boolean Returns TRUE if the AFTER UPDATE trigger has already run, FALSE if not.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
@testVisible
protected boolean afterUpdateHasRun() {
if (isAfterUpdateFirstRun) {
return isAfterUpdateFirstRun = false;
}
return true;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* Checks if the AFTER DELETE static flag has been tripped, and trips the flag if it has not.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @return boolean Returns TRUE if the AFTER DELETE trigger has already run, FALSE if not.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
@testVisible
protected boolean afterDeleteHasRun() {
if (isAfterDeleteFirstRun) {
return isAfterDeleteFirstRun = false;
}
return true;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* Checks if the AFTER UNDELETE static flag has been tripped, and trips the flag if it has not.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @return boolean Returns TRUE if the AFTER UNDELETE trigger has already run, FALSE if not.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
@testVisible
protected boolean afterUndeleteHasRun() {
if (isAfterUndeleteFirstRun) {
return isAfterUndeleteFirstRun = false;
}
return true;
}
}
/**
* ─────────────────────────────────────────────────────────────────────────────────────────────────┐
* Test class for the TriggerHandler virtual class, core component of the Simple Trigger Pattern for
* Salesforce (STP).
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* Copyright (c) 2015 Vivek M. Chawla (@VivekMChawla)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* @author Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @modifiedBy Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @maintainedBy Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @version 1.0
* @created 2015-08-16
* @modified 2015-08-16
* @systemLayer Test
* @see TriggerHandler.class
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* @changes
* v1.0 Vivek.M.Chawla@gmail.com
* 2015-08-16 Initial implementation.
* ─────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
@isTest
private class TriggerHandlerTest {
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* Use one test method to ensure that all "recursion check" methods properly flip their answers
* from FALSE, to TRUE, and that they remain TRUE for at least one more method call after detecting
* the flip.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
static testMethod void recursionCheckMethods_NormalOperation_Success() {
//─────────────────────────────────────────────────────────────────────────┐
// Instantiate a TriggerHandler parent class, then test all of the
// "recursion check" methods.
//─────────────────────────────────────────────────────────────────────────┘
TriggerHandler handler = new TriggerHandler();
//─────────────────────────────────────────────────────────────────────────┐
// Before Insert.
//─────────────────────────────────────────────────────────────────────────┘
System.AssertEquals(false, handler.beforeInsertHasRun());
System.AssertEquals(true, handler.beforeInsertHasRun());
System.AssertEquals(true, handler.beforeInsertHasRun());
//─────────────────────────────────────────────────────────────────────────┐
// Before Update.
//─────────────────────────────────────────────────────────────────────────┘
System.AssertEquals(false, handler.beforeUpdateHasRun());
System.AssertEquals(true, handler.beforeUpdateHasRun());
System.AssertEquals(true, handler.beforeUpdateHasRun());
//─────────────────────────────────────────────────────────────────────────┐
// Before Delete.
//─────────────────────────────────────────────────────────────────────────┘
System.AssertEquals(false, handler.beforeDeleteHasRun());
System.AssertEquals(true, handler.beforeDeleteHasRun());
System.AssertEquals(true, handler.beforeDeleteHasRun());
//─────────────────────────────────────────────────────────────────────────┐
// After Insert.
//─────────────────────────────────────────────────────────────────────────┘
System.AssertEquals(false, handler.afterInsertHasRun());
System.AssertEquals(true, handler.afterInsertHasRun());
System.AssertEquals(true, handler.afterInsertHasRun());
//─────────────────────────────────────────────────────────────────────────┐
// After Update.
//─────────────────────────────────────────────────────────────────────────┘
System.AssertEquals(false, handler.afterUpdateHasRun());
System.AssertEquals(true, handler.afterUpdateHasRun());
System.AssertEquals(true, handler.afterUpdateHasRun());
//─────────────────────────────────────────────────────────────────────────┐
// After Delete
//─────────────────────────────────────────────────────────────────────────┘
System.AssertEquals(false, handler.afterDeleteHasRun());
System.AssertEquals(true, handler.afterDeleteHasRun());
System.AssertEquals(true, handler.afterDeleteHasRun());
//─────────────────────────────────────────────────────────────────────────┐
// After Undelete
//─────────────────────────────────────────────────────────────────────────┘
System.AssertEquals(false, handler.afterUndeleteHasRun());
System.AssertEquals(true, handler.afterUndeleteHasRun());
System.AssertEquals(true, handler.afterUndeleteHasRun());
//─────────────────────────────────────────────────────────────────────────┐
// All done.
//─────────────────────────────────────────────────────────────────────────┘
return;
}
}
/**
* ─────────────────────────────────────────────────────────────────────────────────────────────────┐
* Master trigger for the "Sample Object" object (Sample_Object__c).
*
* This trigger provides dispatch logic for the following Trigger Actions:
*
* [Template Note: Delete the Action Handlers that you are not currently implementing.]
*
* BEFORE INSERT
* BEFORE UPDATE
* BEFORE DELETE
* AFTER INSERT
* AFTER UPDATE
* AFTER DELETE
* AFTER UNDELETE
*
* The framework for dispatching additional Trigger Actions has been left in place. To activate
* these additional dispatch calls, un-comment the appropriate lines of code. Before doing so,
* make sure that the corresponding method in the handler class has been implemented.
*
* IMPORTANT! Under no circumstance should additional logic be placed here, within this Trigger
* definition. Trigger logic should ONLY be implemented by the Trigger Handler class, or classes
* called by the Trigger Handler class.
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* @author Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @modifiedBy Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @maintainedBy Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @version 1.0
* @created YYYY-MM-DD
* @modified YYYY-MM-DD
* @systemLayer Invocation
* @see Sample_Object__c.object
* @see SampleObjectTriggerHandler.class
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* vX.X Vivek.M.Chawla@gmail.com
* YYYY-MM-DD Explanation of the change. Multiple lines can be used to explain the change, but
* each line should be indented till left aligned with the previous description text.
*
* vX.X Vivek.M.Chawla@gmail.com
* YYYY-MM-DD Each change to this file should be documented by incrementing the version number,
* and adding a new entry to this @changes list. Note that there is a single blank
* line between each @changes entry.
* ─────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
trigger SampleObjectTrigger on Sample_Object__c (before insert, before update,
before delete, after insert,
after update, after delete,
after undelete) {
//───────────────────────────────────────────────────────────────────────────┐
// Instantiate the Trigger Handler, then dispatch to the correct Action
// Handler Method (eg. BEFORE INSERT or AFTER UPDATE).
//───────────────────────────────────────────────────────────────────────────┘
//SampleObjectTriggerHandler handler = new SampleObjectTriggerHandler();
/* Before Insert */
//if (trigger.isInsert && trigger.isBefore) {
// handler.beforeInsert(trigger.new);
//}
/* Before Update */
//else if (trigger.isUpdate && trigger.isBefore) {
// handler.beforeUpdate(trigger.old, trigger.oldMap, trigger.new, trigger.newMap);
//}
/* Before Delete */
//else if (trigger.isDelete && trigger.isBefore) {
// handler.beforeDelete(trigger.old, trigger.oldMap);
//}
/* After Insert */
//else if (trigger.isInsert && trigger.isAfter) {
// handler.afterInsert(trigger.new, trigger.newMap);
//}
/* After Update */
//else if (trigger.isUpdate && trigger.isAfter) {
// handler.afterUpdate(trigger.old, trigger.oldMap, trigger.new, trigger.newMap);
//}
/* After Delete */
//else if (trigger.isDelete && trigger.isAfter) {
// handler.afterDelete(trigger.old, trigger.oldMap);
//}
/* After Undelete */
//else if (trigger.isUnDelete) {
// handler.afterUndelete(trigger.new);
//}
}
/**
* ─────────────────────────────────────────────────────────────────────────────────────────────────┐
* Trigger Handler Class for the "Sample Object" object (Sample_Object__c).
*
* This class currently defines handlers for the following Trigger Actions:
*
* [Template Note: Delete the Action Handlers that you are not currently implementing.]
*
* BEFORE INSERT
* BEFORE UPDATE
* BEFORE DELETE
* AFTER INSERT
* AFTER UPDATE
* AFTER DELETE
* AFTER UNDELETE
*
* The framework for unused Action Handlers has been commented out and left in place. To use
* these Handlers, un-comment the appropriate blocks of code by ADDING a single forward-slash "/"
* character to the "/*" that appears directly above the definition of the Action Handler you want
* to activate. Then, add your handler logic directly below the comment that says "Handler logic".
*
* After implementing additional Action Handlers, make sure to un-comment the corresponding lines
* of dispatch code in the Trigger definition.
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* Template Setup:
* 1. Mass-replace Object variable names.
* A. Perform a case-sensitive search and replace of the following string segments:
* 1. SObjects <---- Target Object's Plural Variable Name (eg. "Accounts" or "SampleObjects")
* 2. SObject <---- Target Object's API Name (eg. "Account" or "Sample_Object__c")
* 2. Delete "template help" sections (like this one).
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* @author Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @modifiedBy Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @maintainedBy Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @version 1.0
* @created YYYY-MM-DD
* @modified YYYY-MM-DD
* @systemLayer Invocation
* @see Sample_Object__c.object
* @see SampleObjectTrigger.trigger
* @see TriggerHandler.class
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* @changes
* vX.X Vivek.M.Chawla@gmail.com
* YYYY-MM-DD Explanation of the change. Multiple lines can be used to explain the change, but
* each line should be indented till left aligned with the previous description text.
*
* vX.X Vivek.M.Chawla@gmail.com
* YYYY-MM-DD Each change to this file should be documented by incrementing the version number,
* and adding a new entry to this @changes list. Note that there is a single blank
* line between each @changes entry.
* ─────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
public class SampleObjectTriggerHandler extends TriggerHandler {
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* BEFORE INSERT Handler.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* [Use this space to add comments describing the functional logic of your handler method.]
* [Activate your handler by adding one forward-slash "/" to the "/*" chars above its definition.]
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @param newSObjects List of SObjects to be inserted.
* @return void
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
/*
public void beforeInsert(List<SObject> newSObjects) {
//─────────────────────────────────────────────────────────────────────────┐
// Prevent recursion. This functionality is implemented by the parent class.
//─────────────────────────────────────────────────────────────────────────┘
if (beforeInsertHasRun()) {
return;
}
//─────────────────────────────────────────────────────────────────────────┐
// Handler logic.
//─────────────────────────────────────────────────────────────────────────┘
// [Add your custom handler logic here.]
//─────────────────────────────────────────────────────────────────────────┐
// All done.
//─────────────────────────────────────────────────────────────────────────┘
return;
}
//*/
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* BEFORE UPDATE Handler.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* [Use this space to add comments describing the functional logic of your handler method.]
* [Activate your handler by adding one forward-slash "/" to the "/*" chars above its definition.]
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @param originalSObjects List of SObjects in their original state.
* @param originalSObjectsMap Map of SObjects in their original state.
* @param updatedSObjects List of SObjects with updated data.
* @param updatedSObjectsMap Map of SObjects with updated data.
* @return void
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
/*
public void beforeUpdate(List<SObject> oldSObjects,
Map<ID, SObject> oldSObjectsMap,
List<SObject> updatedSObjects,
Map<ID, SObject> updatedSObjectsMap) {
//─────────────────────────────────────────────────────────────────────────┐
// Prevent recursion. This functionality is implemented by the parent class.
//─────────────────────────────────────────────────────────────────────────┘
if(beforeUpdateHasRun()) {
return;
}
//─────────────────────────────────────────────────────────────────────────┐
// Handler logic.
//─────────────────────────────────────────────────────────────────────────┘
// [Add your custom handler logic here.]
//─────────────────────────────────────────────────────────────────────────┐
// All done.
//─────────────────────────────────────────────────────────────────────────┘
return;
}
//*/
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* BEFORE DELETE Handler.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* [Use this space to add comments describing the functional logic of your handler method.]
* [Activate your handler by adding one forward-slash "/" to the "/*" chars above its definition.]
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @param deletedSObjects List of SObjects that are going to be deleted.
* @param deletedSObjectsMap Map of SObjects that are going to be deleted.
* @return void
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
/*
public void beforeDelete(List<SObject> deletedSObjects,
Map<ID, SObject> deletedSObjectsMap) {
//─────────────────────────────────────────────────────────────────────────┐
// Prevent recursion. This functionality is implemented by the parent class.
//─────────────────────────────────────────────────────────────────────────┘
if(beforeDeleteHasRun()) {
return;
}
//─────────────────────────────────────────────────────────────────────────┐
// Handler logic.
//─────────────────────────────────────────────────────────────────────────┘
// [Add your custom handler logic here.]
//─────────────────────────────────────────────────────────────────────────┐
// All done.
//─────────────────────────────────────────────────────────────────────────┘
return;
}
//*/
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* AFTER INSERT Handler.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* [Use this space to add comments describing the functional logic of your handler method.]
* [Activate your handler by adding one forward-slash "/" to the "/*" chars above its definition.]
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @param newSObjects List of SObjects that were just inserted.
* @param newSObjectsMap Map of SObjects that were just inserted.
* @return void
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
/*
public void afterInsert(List<SObject> newSObjects,
Map<ID, SObject> newSObjectsMap) {
//─────────────────────────────────────────────────────────────────────────┐
// Prevent recursion. This functionality is implemented by the parent class.
//─────────────────────────────────────────────────────────────────────────┘
if(afterInsertHasRun()) {
return;
}
//─────────────────────────────────────────────────────────────────────────┐
// Handler logic.
//─────────────────────────────────────────────────────────────────────────┘
// [Add your custom handler logic here.]
//─────────────────────────────────────────────────────────────────────────┐
// All done.
//─────────────────────────────────────────────────────────────────────────┘
return;
}
//*/
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* AFTER UPDATE Handler.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* [Use this space to add comments describing the functional logic of your handler method.]
* [Activate your handler by adding one forward-slash "/" to the "/*" chars above its definition.]
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @param originalSObjects List of SObjects in their original state.
* @param originalSObjectsMap Map of SObjects in their original state.
* @param updatedSObjects List of SObjects with updated data.
* @param updatedSObjectsMap Map of SObjects with updated data.
* @return void
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
/*
public void afterUpdate(List<SObject> originalSObjects,
Map<ID, SObject> originalSObjectsMap,
List<SObject> updatedSObjects,
Map<ID, SObject> updatedSObjectsMap) {
//─────────────────────────────────────────────────────────────────────────┐
// Prevent recursion. This functionality is implemented by the parent class.
//─────────────────────────────────────────────────────────────────────────┘
if(afterUpdateHasRun()) {
return;
}
//─────────────────────────────────────────────────────────────────────────┐
// Handler logic.
//─────────────────────────────────────────────────────────────────────────┘
// [Add your custom handler logic here.]
//─────────────────────────────────────────────────────────────────────────┐
// All done.
//─────────────────────────────────────────────────────────────────────────┘
return;
}
//*/
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* AFTER DELETE Handler.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* [Use this space to add comments describing the functional logic of your handler method.]
* [Activate your handler by adding one forward-slash "/" to the "/*" chars above its definition.]
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @param deletedSObjects List of SObjects that were just deleted.
* @param deletedSObjectsMap Map of SObjects that were just deleted.
* @return void
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
/*
public void afterDelete(List<SObject> deletedSObjects,
Map<ID, SObject> deletedSObjectsMap) {
//─────────────────────────────────────────────────────────────────────────┐
// Prevent recursion. This functionality is implemented by the parent class.
//─────────────────────────────────────────────────────────────────────────┘
if(afterDeleteHasRun()) {
return;
}
//─────────────────────────────────────────────────────────────────────────┐
// Handler logic.
//─────────────────────────────────────────────────────────────────────────┘
// [Add your custom handler logic here.]
//─────────────────────────────────────────────────────────────────────────┐
// All done.
//─────────────────────────────────────────────────────────────────────────┘
return;
}
//*/
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* AFTER UNDELETE Handler.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* [Use this space to add comments describing the functional logic of your handler method.]
* [Activate your handler by adding one forward-slash "/" to the "/*" chars above its definition.]
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @param restoredSObjects List of SObjects recovered from the Recycle Bin.
* @return void
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
/*
public void afterUndelete(List<SObject> restoredSObjects) {
//─────────────────────────────────────────────────────────────────────────┐
// Prevent recursion. This functionality is implemented by the parent class.
//─────────────────────────────────────────────────────────────────────────┘
if(afterUndeleteHasRun()) {
return;
}
//─────────────────────────────────────────────────────────────────────────┐
// Handler logic.
//─────────────────────────────────────────────────────────────────────────┘
// [Add your custom handler logic here.]
//─────────────────────────────────────────────────────────────────────────┐
// All done.
//─────────────────────────────────────────────────────────────────────────┘
return;
}
//*/
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* [Create helper methods to keep the code in your action handlers tight and easy to understand.]
* [All helper methods should be marked "private".]
* [Adding the @testVisible annotation to all private helper methods will facilitate Unit Tests.]
* [Use this Method Header Comments template to document all helper methods you create.]
* ────────────────────────────────────────────────────────────────────────────────────────────────
* @param NAME DESCRIPTION (MENTION PARAMETER TYPE IN DESCRIPTION)
* @param NAME DESCRIPTION (MENTION PARAMETER TYPE IN DESCRIPTION)
* @return TYPE DESCRIPTION (MENTION LIKELY VALUES IN DESCRIPTION)
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
@testVisible
private boolean helperMethodBoilerplate() {
//─────────────────────────────────────────────────────────────────────────┐
// [All inline comments terminate at column 80.]
//─────────────────────────────────────────────────────────────────────────┘
if (1 == 1) {
//───────────────────────────────────────────────────────────────────────┐
// [Even after indenting, inline comments still terminate at column 80.]
//───────────────────────────────────────────────────────────────────────┘
if (1 != 2) {
//─────────────────────────────────────────────────────────────────────┐
// [See? Another indent but the comments STILL terminate at column 80!]
//─────────────────────────────────────────────────────────────────────┘
if (1 > 0) {
//───────────────────────────────────────────────────────────────────┐
// [Use these sample comments to copy-paste this style in your code.]
// [Also, consider using this System.Debug() template in your code.]
//───────────────────────────────────────────────────────────────────┘
System.Debug(System.LoggingLevel.DEBUG
,'\n-->Inside CustomTriggerHandler.helperMethodBoilerplate()'
+'\n-->This is an example of how to use formatting in your debug messages.'
+'\n-->The variables shown are defined by the parent class.'
+'\n-->Key Variables:'
+'\n---->isBeforeInsertFirstRun : ' + isBeforeInsertFirstRun
+'\n---->isBeforeUpdateFirstRun : ' + isBeforeUpdateFirstRun
+'\n---->isBeforeDeleteFirstRun : ' + isBeforeDeleteFirstRun
+'\n---->isAfterInsertFirstRun : ' + isAfterInsertFirstRun
+'\n---->isAfterUpdateFirstRun : ' + isAfterUpdateFirstRun
+'\n---->isAfterDeleteFirstRun : ' + isAfterDeleteFirstRun
+'\n---->isAfterUndeleteFirstRun : ' + isAfterUndeleteFirstRun
+'\n*~*');
}
}
}
//─────────────────────────────────────────────────────────────────────────┐
// All done.
//─────────────────────────────────────────────────────────────────────────┘
return true;
}
}
/**
* ─────────────────────────────────────────────────────────────────────────────────────────────────┐
* Test class containing methods that cover code implemented by the SampleObjectTriggerHandler class.
*
* Write additional general information about the program, class, method or variable which follows
* this comment block. Use as many lines as necessary, but keep each line within the 100 character
* wide box defined by the top ───┐ and bottom ───┘ brackets.
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* @author Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @modifiedBy Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @maintainedBy Vivek M. Chawla <Vivek.M.Chawla@gmail.com>
* @version 1.0
* @created YYYY-MM-DD
* @modified YYYY-MM-DD
* @systemLayer Test
* @see Sample_Object__c.object
* @see SampleObjectTrigger.trigger
* @see SampleObjectTriggerHandler.class
* @see TriggerHandler.class
* ──────────────────────────────────────────────────────────────────────────────────────────────────
* @changes
* vX.X Vivek.M.Chawla@gmail.com
* YYYY-MM-DD Explanation of the change. Multiple lines can be used to explain the change, but
* each line should be indented till left aligned with the previous description text.
*
* vX.X Vivek.M.Chawla@gmail.com
* YYYY-MM-DD Each change to this file should be documented by incrementing the version number,
* and adding a new entry to this @changes list. Note that there is a single blank
* line between each @changes entry.
* ─────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
@isTest
private class SampleObjectTriggerHandlerTest {
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* Write a single, summary sentence for this test method.
*
* Write additional general information about this test method. Describe the functionality that
* this method is designed to test, including both positive and negative test cases.
*
* While writing this comment, use as many lines as necessary, but keep each line within the 100
* character wide box defined by the top ───┐ and bottom ───┘ brackets.
* ────────────────────────────────────────────────────────────────────────────────────────────────
* Test Method Naming Conventions:
*
* All methods marked by the @testMethod annotation should be named in a manner that describes the
* functional test logic being implemented by using the following pattern:
*
* MethodName_StateUnderTest_ExpectedBehavior
*
* Examples: enrollStudent_InvalidData_ExceptionThrown(), addFollower_ValidData_Success()
* ────────────────────────────────────────────────────────────────────────────────────────────────
* FUNCTIONAL TEST LOGIC
* ────────────────────────────────────────────────────────────────────────────────────────────────
*
* PLEASE FOLLOW THIS LAYOUT EXAMPLE:
*
* 1. Step one.
* A. Sub-step 1-A
* B. Sub-step 1-B
* > Additional details within sub-step 1-B
* 2. Step two.
* A. Sub-step 2-A
* > Additional details within sub-step 2-A
* > ... (process / steps repeat)
* > Final detail within sub-step 2-A
* N. Steps continue until functional logic explanation is complete.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
static testMethod void beforeInsert_ValidData_Success() {
//─────────────────────────────────────────────────────────────────────────┐
// TODO: IMPLEMENT UNIT TEST
//─────────────────────────────────────────────────────────────────────────┘
return;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* If you can't use the ideal template above, just write a quick summary of what is being tested.
* Use the same naming conventions detailed in the example above.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
static testMethod void beforeUpdate_ValidData_Success() {
//─────────────────────────────────────────────────────────────────────────┐
// TODO: IMPLEMENT UNIT TEST
//─────────────────────────────────────────────────────────────────────────┘
return;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* If you can't use the ideal template above, just write a quick summary of what is being tested.
* Use the same naming conventions detailed in the example above.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
static testMethod void beforeDelete_ValidData_Success() {
//─────────────────────────────────────────────────────────────────────────┐
// TODO: IMPLEMENT UNIT TEST
//─────────────────────────────────────────────────────────────────────────┘
return;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* If you can't use the ideal template above, just write a quick summary of what is being tested.
* Use the same naming conventions detailed in the example above.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
static testMethod void afterInsert_ValidData_Success() {
//─────────────────────────────────────────────────────────────────────────┐
// TODO: IMPLEMENT UNIT TEST
//─────────────────────────────────────────────────────────────────────────┘
return;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* If you can't use the ideal template above, just write a quick summary of what is being tested.
* Use the same naming conventions detailed in the example above.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
static testMethod void afterUpdate_ValidData_Success() {
//─────────────────────────────────────────────────────────────────────────┐
// TODO: IMPLEMENT UNIT TEST
//─────────────────────────────────────────────────────────────────────────┘
return;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* If you can't use the ideal template above, just write a quick summary of what is being tested.
* Use the same naming conventions detailed in the example above.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
static testMethod void afterDelete_ValidData_Success() {
//─────────────────────────────────────────────────────────────────────────┐
// TODO: IMPLEMENT UNIT TEST
//─────────────────────────────────────────────────────────────────────────┘
return;
}
/**
* ───────────────────────────────────────────────────────────────────────────────────────────────┐
* If you can't use the ideal template above, just write a quick summary of what is being tested.
* Use the same naming conventions detailed in the example above.
* ───────────────────────────────────────────────────────────────────────────────────────────────┘
*/
static testMethod void afterUndelete_ValidData_Success() {
//─────────────────────────────────────────────────────────────────────────┐
// TODO: IMPLEMENT UNIT TEST
//─────────────────────────────────────────────────────────────────────────┘
return;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment