Preface: this post is part of the Write Your First Advanced Trigger series.
For this trigger, we’re going to write a test class that any developer would be proud of!
The more you code you write, the more you’ll understand why good test classes are so important!
Make sure to always follow the Principles of a Good Test Class.
Here’s the overall plan for this test class:
Users: 20 records
Territories: 200 records
Territory Members: 2,000 records (~10 per Territory)
Accounts: 200 records
Scenario #1: Insert 200 Accounts with Zip Codes
Scenario #2: Update 200 Accounts without Zip Codes (this shouldn’t work!)
Scenario #3: Update 200 Accounts with Zip Codes
We’ll use System.assertEquals to make sure each Account has the right number of AccountShare records according to their Territory
This code will accomplish all the above and bring us to 100% code coverage:
@isTest public class TestShareTerr { static testMethod void testShareTerritories() { // Principle #1: Create records from scratch! // Insert 20 Users List<User> users = new List<User>();for (Integer i = 0; i < 20; i++) {User u = new User(); u.FirstName = 'Elsa'; u.LastName = 'of Arendelle'; u.Email = 'elsa@disney.com'; u.Alias = 'elsa' + i; u.Username = 'elsa' + i + '@disney.com'; u.LocaleSidKey = 'en_US'; u.TimeZoneSidKey = 'GMT'; u.ProfileID = '00ei0000000rTfz'; u.LanguageLocaleKey = 'en_US'; u.EmailEncodingKey = 'UTF-8'; users.add(u); } insert users; // Insert 200 Territories List<Territory__c> terrs = new List<Territory__c>();for (Integer i = 0; i < 200; i++) {Territory__c t = new Territory__c();t.Zip_Code__c = String.valueOf(1000 + i);terrs.add(t); } insert terrs; // Insert up to 20 Members per Territory List<Territory_Member__c> tms = new List<Territory_Member__c>();for (Integer i = 0; i < 200; i++) {// Here we "cast", aka transform the Double into an IntegerInteger count = Integer.valueOf(Math.random() * 20); for (Integer j = 0; j < count; j++) {Territory_Member__c tm = new Territory_Member__c(); tm.Territory__c = terrs[i].Id; tm.User__c = users[j].Id; tms.add(tm); } } insert tms; // "Setup" data has been entered, begin testing // This trick gives us a new set of Governor Limits!Test.startTest();// Principle #4: Test in bulk (200+ records)! // Test #1: 200 new Accounts with Zip Codes List<Account> accs = new List<Account>();for (Integer i = 0; i < 200; i++) {Account a = new Account(); a.Name = 'Disney';String zip = String.valueOf(1000 + i);a.BillingPostalCode = zip; accs.add(a); } insert accs; // Principle #2: Assert your results! // Get all Accounts and their AccountShares accs = [SELECT Id, BillingPostalCode,(SELECT Id, AccountId, AccountAccessLevel, OpportunityAccessLevel, UserOrGroupId FROM Shares WHERE RowCause = 'Manual')FROM Account]; // Create a Map of all Territories and their Members Map<String, Territory__c> terrMap = new Map<String, Territory__c>(); terrs = [SELECT Id, Zip_Code__c,(SELECT Id, User__c FROM Territory_Members__r)FROM Territory__c]; for (Territory__c t : terrs) { terrMap.put(t.Zip_Code__c, t); } // Assert each Account has the right AccountShare count for (Account a : accs) { Territory__c t = terrMap.get(a.BillingPostalCode);Integer shareCount = a.Shares.size(); Integer memberCount = t.Territory_Members__r.size(); System.assertEquals(shareCount, memberCount);} // Principle #3: Test things that shouldn't work! // Test #2: 200 updated Accounts without Zip Codes for (Account a : accs) {a.BillingPostalCode = null;} update accs; // Assert that each Account has no Account Shares! accs = [SELECT Id, BillingPostalCode,(SELECT Id, AccountId, AccountAccessLevel, OpportunityAccessLevel, UserOrGroupId FROM Shares WHERE RowCause = 'Manual')FROM Account]; for (Account a : accs) { Integer shareCount = a.Shares.size();System.assertEquals(0, shareCount);} // Test #3: 200 updated Accounts with Zip Codesfor (Integer i = 0; i < 200; i++) {String zip = String.valueOf(1000 + i); accs[i].BillingPostalCode = zip; } update accs; // Assert each Account has the right AccountShare count accs = [SELECT Id, BillingPostalCode,(SELECT Id, AccountId, AccountAccessLevel, OpportunityAccessLevel, UserOrGroupId FROM Shares WHERE RowCause = 'Manual')FROM Account]; for (Account a : accs) { Territory__c t = terrMap.get(a.BillingPostalCode);Integer shareCount = a.Shares.size(); Integer memberCount = t.Territory_Members__r.size(); System.assertEquals(shareCount, memberCount);} Test.stopTest(); } }
Looks like a lot of code, but each section is very basic and non-groundbreaking!
Never give up, your dreams are on the line!
CONGRATULATIONS! You’re now able to write advanced triggers! Enjoy your new found powers, you may now call yourself a Salesforce developer without hesitation!
Now go and celebrate your achievement by watching a similarly epic journey!
Next post: The biggest secret Apex developers don’t want you to know!
Create a custom object ‘Case Solution’ with fields Case Description, Resolution.
– Create a custom field ‘Accept Solution’ as a checkbox on the Case object.
– Create a custom field ‘Resolution’ as a text area on Case Object.
– Whenever a case has the status ‘Closed’ and its ‘Accept Solution’ checkbox is selected; a new Case Solution record should be created.
– Code should be able to handle bulk data; meaning multiple cases can be added/updated using Import Wizard or Data Loader.
– Code should adhere to programming best practices like
– Standardized Naming conventions
– Apt utilization of collection elements
– Follow of Trigger pattern
– Avoiding Trigger recurrence on same object
– Proper Code comments
– Keeping Salesforce Governor limits into consideration
– Create an Apex test class to ensure developed code can be deployed; adhere to best practices of testing framework
Plss sir how to run this code
Hi Gys,
When we are updating the accounts, we are assigning the same zipCode values which were assigned earlier, still how the test class can run as we are not updating any new value. And we implemented the rule in trigger that, if old value is not equal to new value, Only then the code will run. But here both old and new values of zipcodes are same.
Can Someone please clear my doubt
Hi david,
when I execute the below line of code in anonymous block, and debug
Integer count = Integer.valueOf(Math.random() * 20);
system.debug(‘count is’+count);
iam getting a count of 16.
would u please clarify ..on this point.
What are you expecting other than 16? Check your order of operations =)
@HttpGet
global static MyWrapper.resWrapper doGet(){
try{
RestRequest req = RestContext.request;
MyWrapper.ReqWrapper obj = new MyWrapper.ReqWrapper(req.params);
if(obj != null && obj.param1 != null){
return new MyWrapper.ResWrapper(parameters);
List lstWrap = new List();
if(Condition){
return new MyWrapper.MyLeadResponseWrapper(lstWrap, params);
}
return new MyWrapper.ResWrapper(parameters);
}
catch(Exception ex){
return new MyWrapper.ResWrapper(parameters);
}
}
How can we insert wrapper class of https return type in test class . please help me
I noticed that your logic for zip codes would make the codes invalid (for a us 5 digit) after the 10th iteration. Since you’re doing less than 1000 iterations, you can solve that problem like this:
if(i<10) zip = String.valueOf('1000' + i);
if(i<100) zip = String.valueOf('100' + i);
if(i<1000) zip = String.valueOf('10' + i);
Thanks for finding this!
You are probably right but I’m having a heck of a time following you. Very curious to know specifically where it’ll break!
Hi David,
It’s a great site, and I’m learning a lot! I’m planning to learn a lot more from you too. I own a business, it’s a small operation and things change quickly. I don’t have the time to wait on developers to learn my org each time I need a small change so I’m looking to develop myself. This has bee the best resource I’ve found so far!
John: I think I understand what is happening here. String.valueOf() takes an integer and returns the string that represents that integer. So the code should be written as:
String zip = String.valueOf(10000 + i)
If you use the .valueOf() method on a String class it returns an Integer. I thought the same as you at first because in the code it looks is like the method takes a string because there were 3 zeroes not 4. But it takes an integer so .valueOf(10000 + i) where i is 201 would result in the integer 10201 which would then be converted to a string by the method.
I’m new to all of this. so let me know if that sounds right.
Sage
The iterator “i”, would become the number after the 10th loop.
This essentially means that your concatenating 2 integers to the original 4; bringing you to 6 digits for your zip, 7 after the 100th iteration.
I think this is what John is referring to.
Could also handle like this:
for (Integer i = 0; i < 200; i++) {
String zip = String.valueOf(i + '0000').left(5);
accs[i].BillingPostalCode = zip;
}
https://screencast.com/t/Cm3D6GzIxq
Hi David,
I am facing issues with this test class. “” System.AssertException.Assertion failed :Expected :0, Actual :3 “”. Any guess ?
Hi, you may have spotted another post from me but I am now on day 6 of my SalesForce programming journey but have run into a brick wall and my posting on Developer Forums has yet to turn up an answer. I have successfully written an APEX class that is executed via Javascript behind a custom button to take data from a temporary holding object and and create a Case with 2 clients etc. It all works fine and I’ve tested it thoroughly but I cannot correctly write an @istest class to execute my code. The code fails in the SELECT statement with “List has no rows for assignment to SObject “. I’d appreciate some pointers of what I need to read up on to solve this:
Javascirpt:
Class (first few lines):
global with sharing class createWebReferralCase{
//Class to handle referrals from the web site
//Returns a string to indicate success or failure
WebService static String createNewCase(String webRefName)
{
System.Debug(‘webRefName = ‘ + webRefName); // returns “WR~1004” when executed from button
// returns null when executed from test class
// ** Start create new case **
// extract to data from Web_Referral for Mediation_Case
Web_Referral__c ref = [Select r.C1_Last_Name__c,
r.Outreach__c
From Web_Referral__c r
Where r.Name = :webRefName LIMIT 1];
// create new case etc, etc
Test Class:
@isTest
// Test class for the web referrral create case button code.
private class createWebReferralCaseTest {
testMethod static void test() {
String alertText = createWebReferralCase.createNewCase(‘WR~1004’);
System.debug(alertText);
}
}
Javascript for button:
{!REQUIRESCRIPT(“/soap/ajax/15.0/connection.js”)}
{!REQUIRESCRIPT(“/soap/ajax/15.0/apex.js”)}
var result = sforce.apex.execute(
“createWebReferralCase”,
“createNewCase”,
{webRefName:”{!Web_Referral__c.Name}”});
I should also mention I have tried creating and inserting a test record in the test class and passing testRecord.Name but seems to pass an Id e.g AX0000gFXX and still fails to execute the SELECT query in the same way.
You’re on the right track – definitely need to create the record from scratch in your test!
Can you post your updated code?
David
Hi David,
I tried to run this code but its giving me the error:
System.DmlException: Insert failed. First exception on row 0 with id a0N9000000A9lIpEAJ; first error: INVALID_FIELD_FOR_INSERT_UPDATE, cannot specify Id in an insert call: [Id]
I dont understand where i went wrong.
Thanks
Check out this post for help!
https://www.sfdc99.com/2014/02/22/debug-your-code-with-system-debug/
Hi David,
Writing a test class for trigger seems tedious than writing a trigger class.
Can that be simplified any further?? i am still having problem writing a test class :(
That is 100% true. Proper testing is pretty rigorous and arguably more important than the trigger itself!
Once you get used to coding, you pretty much write (or at least imagine writing) the test class while writing the trigger. You’ll find that there are ways to write a trigger such that testing it is much easier!
Practice!
Superb! Thanks David!
Thanks David, this is awesome stuff. I really enjoy reading your blog posts and wish all programming tutorials would be this entertaining :)
Awww, thanks Michelle!!
Let me know how the programming journey goes for you!
David