It's exciting to see all of the new members on the Salesforce.com message board that are just getting into cloud computing. Some of the most common questions revolve around how to write, test and debug bulk triggers. Programming for a multi-tenant environment is different than developing for a dedicated server and it's understandable that developers coming from a Java or .NET background will have some sort of ramp up time.
This article does not go over all aspects of triggers or bulk processing, so please see the Apex docs for more info. There is some really good documentation and tutorials on writing Apex triggers and unit testing but it seems to be spread out over different documents and wiki pages. My goal is to pull together all of this disparate info together into one tutorial and demonstrate how to write, and not write, triggers for Salesforce.com.
What exactly is a trigger? If you come from a SQL Server or Oracle background you will have some trigger experience, however a trigger in Salesforce.com is slightly different. According to the Apex documentation, a trigger is an Apex script that executes before or after insert, update, or delete events occur, such as before object records are inserted into the database, or after records have been deleted. When a trigger fires it can process anywhere from 1 to 200 records so all triggers should be written to accommodate bulk transactions. Examples of single record and bulk transactions include:
- Data import
- Bulk Force.com API calls
- Mass actions, such as record owner changes and deletes
- Recursive Apex methods and triggers that invoke bulk DML statements
First I’m going to outline the wrong way to write triggers. I think this is important to demonstrate, as it is how must new developers begin. Remember, this is wrong way to write a trigger. Here is the use case for the trigger. Each time an Account is created or updated, you want to fetch the Account owner’s “favorite color” from a custom field on their User record and write it into the Account’s record.
Coming from a Java or .NET background, I would probably write a trigger something like the following. This would be perfectly acceptable if you only needed to update a single record each time.
Here is the test class for the trigger. The first unit test (testSingleInsert) runs fine as it does not encounter any governors. The second unit test (testBulkInsert) will fail when trying to insert 200 records with the followning message:
System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, AddOwnerColor: execution of BeforeInsert caused by: System.Exception: Too many SOQL queries: 101
There are a number of governors that the Apex runtime engine enforces for specific contexts and entry points (trigger, tests, anonymous block execution, controllers and WSDL methods). You should study “Understanding Execution Governors and Limits” religiously as it will dictate how you develop for the Force.com platform. It is suggested that you test bulk processing with at least 25 records but I typically use 200. My reasoning is that if you only use 25 records and do not utilize the testing runtime context (Test.startTest) your trigger will run without errors. However, once you put this trigger into Production it will throw an exception when processing the 21st record. The reason is that triggers have a 20 SOQL query limit while in RunTest it has a limit of 100 SOQL queries.
Writing the Bulk Safe Trigger
To write bulk safe triggers, it is critical that you understand and utilize sets and maps. Sets are used to isolate distinct records, while maps are name-value pairs that hold the query results retrievable by record id. So here is the bulk safe trigger code. When the trigger fires we initially create a set containing all of the distinct OwnerIds for the records being processed (1 -> 200). Then we query to find all of the User records for the OwnerIds in the set. This returns a map with the UserId as the key and the User object as the value. We then iterate over the list of Accounts in the trigger, use the map's get method to fetch the correct User object by its OwnerId and write the User's favorite color into a custom field (Owner_Favorite_Color__c) on the Account. When the records are committed to the database, the User's color auto-magically appears!
To test the trigger, we've made a few modifications to test for governor limits. You can use the system static startTest and stopTest methods to ensure your code runs properly in the runtime context. You should set up all of your variables, data structures, etc and then call startTest. After you call this method, the limits that get applied are based on your first DML statement (INSERT, UPSERT, etc). The stopTest method marks the point in your test code when your test ends and any post assertions are done in the original context. The graphic below displays the resource summary in both the RunTest context as well testing context.
We've also added the modification to allow the DML statement to run as a specific user. According to the Apex docs, generally all Apex scripts run in system mode, and the permissions and record sharing of the current user are not taken into account. The system method runAs enables you to write test methods that change user contexts to either an existing user or a new user. All of that user's record sharing is then enforced.
When you run your tests you should see the following: