Preface – This post is part of the Write Your First Intermediate Trigger series.
Let’s write a test class for our deduping trigger, using Apex testing best practices!
Here’s a look at the test class – we’ll do a line-by-line breakdown immediately afterwards:
@isTest public class TestFindDupes { static testMethod void testDupes() { // Let's create our records from scratch! Contact c = new Contact(); c.FirstName = 'Stephen'; c.LastName = 'Curry';c.Email = 'stephcurry@gsw.com'; insert c;// Now let's create a dupe lead Lead dupeLead = new Lead(); dupeLead.FirstName = 'Steph'; dupeLead.LastName = 'Curry'; dupeLead.Company = 'Golden State Warriors';dupeLead.Email = 'stephcurry@gsw.com';// This is a special way of doing "risky" things try {insert dupeLead;} catch (Exception e) { System.debug('An error happened, as predicted!'); } // Now we try to find our dupe lead, by emailList<Lead> dupes = [SELECT Id FROM Lead WHERE Email = 'stephcurry@gsw.com'];System.assertEquals(0, dupes.size());// Now we "break" our trigger by inserting a non-dupe Lead legitLead = new Lead(); legitLead.FirstName = 'David'; legitLead.LastName = 'Lee'; legitLead.Company = 'Golden State Warriors';legitLead.Email = 'dlee@gsw.com'; insert legitLead;// Now we try to find our legit lead, by emailList<Lead> legits = [SELECT Id FROM Lead WHERE Email = 'dlee@gsw.com'];System.assertEquals(1, legits.size());} }
Don’t be intimidated – there’s a decent amount of code but none of it is complicated!
Let’s do a line by line analysis:
@isTest public class TestFindDupes { static testMethod void testDupes() { ... } }
The above is the usual “fluff” that you don’t need to worry about.
// Let's create our records from scratch! Contact c = new Contact(); c.FirstName = 'Stephen'; c.LastName = 'Curry';c.Email = 'stephcurry@gsw.com'; insert c;
We’re creating a new contact from scratch (Testing Best Practice #1!) that will serve as the “original” record to dedupe against. Remember that we use the email address to know if it’s a dupe or not! There can never be another Steph Curry!
// Now let's create a dupe lead Lead dupeLead = new Lead(); dupeLead.FirstName = 'Steph'; dupeLead.LastName = 'Curry'; dupeLead.Company = 'Golden State Warriors';dupeLead.Email = 'stephcurry@gsw.com';
We’re creating a dupe lead (by email address) – remember that nothing happens until we insert it!
// This is a special way of doing "risky" things try {insert dupeLead;} catch (Exception e) { System.debug('An error happened, as predicted!'); }
You don’t have to understand this part above but I’ll explain what’s happening anyway.
This technique is known as a try/catch statement. It lets you “try” to do something that may break your code (like divide by zero, or in our case, insert a dupe). If an error is “caught”, it doesn’t break your code and lets you move on.
Without try/catch, our code would break here! We’ll cover this and System.debug in a future chapter!
// Now we try to find our dupe lead, by emailList<Lead> dupes = [SELECT Id FROM Lead WHERE Email = 'stephcurry@gsw.com'];System.assertEquals(0, dupes.size());
Now that we attempted to create the dupe lead, we try to find it using SOQL. Test classes are run in a “clean” version of your org – all records are wiped! So the only records that exist are the ones we created in this test.
We use System.assertEquals() to check that no leads with the duplicate email are found. We check this by making sure there are exactly 0 leads that are returned by the SOQL query. If a lead is found, it means that our trigger isn’t working as expected, and System.assertEquals() will error out the test. Using System.assertEquals() is Testing Best Practice #2!
// Now we "break" our trigger by inserting a non-dupe Lead legitLead = new Lead(); legitLead.FirstName = 'David'; legitLead.LastName = 'Lee'; legitLead.Company = 'Golden State Warriors';legitLead.Email = 'dlee@gsw.com'; insert legitLead;// Now we try to find our legit lead, by emailList<Lead> legits = [SELECT Id FROM Lead WHERE Email = 'dlee@gsw.com'];System.assertEquals(1, legits.size());
We’re creating a lead that isn’t a dupe now and checking that our trigger doesn’t incorrectly catch it! By testing scenarios that our trigger shouldn’t work on, we’re fulling Testing Best Practice #3!
Login to Sfdc99 to see this test class in action!
Very proud of you for making it through the first four chapters of Sfdc99! We’re ready to start moving on to some more advanced techniques!
Very soon now you’ll be a full-fledged Salesforce developer!
Next post: Introduction to Governor Limits!
Hi David,
In our Trigger, we tried finding if there are any contacts with same email address as that on Lead.
But in the Test class I see that we are finding if the email address on Lead is same as on any other Lead.
Can you please let me know why we checked if the email address is same on any other Lead and not the Contact record.
Hi david,
Could you please explain about @testSetup used during test classes.
Thanks,
Chetan
Check this out!
https://salesforce.stackexchange.com/questions/163568/how-to-use-the-annotated-testsetup-method-in-tests
Hi David,
Great site!
I have an question in relation to the above test. The trigger in the exercise looks for matching Contacts that have the same email as the Lead. However in your test class, the following lines:
List dupes = [SELECT Id FROM Lead
WHERE Email = ‘stephcurry@gsw.com’];
System.assertEquals(0, dupes.size());
…… Checks if duplicates LEADS exist with the same email address as the Lead that we have just inserted. Shouldn’t we be checking if there is a CONTACT with the same email?
I rewrote the code in my org to look like this:
List dupes = [select Id From Contact where email = :dupeLead.email];
System.assertEquals(1, dupes.size());
Similarly for the negative testing – we should be looking for the Contact with matching email as the non duplicate Lead and the list size should be 0.
The result was 86% test coverage on the trigger.
Let me know your thoughts
Neil
Hey Neil!
We check for leads since we inserted the contact first. Meaning – the contact is the “master” record and the lead is the dupe. With our trigger, the lead “insert” should have failed and the query should not have returned any records!
The assert you did on the contact side actually is still very helpful – it would be a “positive” test that checks to see a clean contact is inserted properly. The lead insert is the negative test case.
Code coverage is the same regardless which method you test – this is because asserts rarely impact code coverage. The act of inserting records did all the work!
Hope this makes sense and good luck on your learning journey!
David
Hi David, can You pls check why am always getting count 0 even after inserting a contact in the account its always showing 0 only. even system.debug is giving the value 0. PLZ HELP!!
trigger countcontact on Contact (after insert) {
Set Idcon = new set();
Integer contNum=0;
List accLs = new List();
For(Contact con:trigger.new){
Idcon.add(con.Id);
}
contNum = [select Count() from Contact where Accountid in:Idcon ];
System.debug(‘count1 : ‘ + contNum);
for(Contact con:trigger.new){
Account acc1=new Account();
acc1.Contacts_Number__c=contNum;
accLs.add(acc1);
}
Update accLs;
}
I also noticed that my code coverage was 0 (using the code verbatim in FindDupes and TestFindDupes). Any hints on what else I may be missing?
Is it possible to write a trigger that fires off of page views? We have a need to record when users view leads, contacts and opportunities. My first thought is to record the page views in a custom object but I am stuck on triggering activity off of a view not an edit.
Possible with Visualforce! But super hacky!
David,
This trigger looks great and would help me out a bunch. I tried deploying it and am getting an error on line 29 of the test
System.assertEquals(0, dupes.size());
The errors says… TestFindDupes.testDupes(), Details: System.AssertException: Assertion Failed: Expected: 0, Actual: 1 Class.TestFindDupes.testDupes: line 29, column 1
I pulled the test code right from your example. I created the custom field in lead, but I don’t know why it is returning the lead that should have been caught.
It’s possible there is another apex trigger in your org that is breaking the test class.
my code is
trigger CaseTrigger on Case (before insert,before update,after insert,after update) {
if((trigger.isBefore && trigger.isInsert) || (trigger.isBefore && trigger.IsUpdate)){
set accIdSet = new Set();
for(Case cs : trigger.new){
if(cs.AccountId !=null){
accIdSet.add(cs.AccountId);
}
}
system.debug(‘accIdSet==>’+accIdSet);
if(accIdSet !=null && accIdSet.size()>0){
Map accMap = new Map([Select id,Name,Device_Replacement__c from account where id in :accIdSet]);
system.debug(‘accMap==>’+accMap);
for(Case cs: trigger.new){
if(cs.Roku_Offer__c !=null && accMap.get(cs.AccountId).Device_Replacement__c !=’Eligible’){
cs.addError(‘Roku offer is not available on this Case Account.’);
}
}
}
}
if(trigger.isAfter && (trigger.isUpdate || trigger.isInsert)){
system.debug(‘trigger.isAfter==>’);
Map accUpdateMap = new Map();
for(Case cs:trigger.new){
if(cs.AccountId !=null && cs.Roku_Offer__c !=null){
accUpdateMap.put(cs.AccountId,new Account(id=cs.AccountId,Device_Replacement__c=’Completed’));
}
}
system.debug(‘accUpdateMap==>’+accUpdateMap);
if(accUpdateMap !=null && accUpdateMap.size()>0){
update accUpdateMap.values();
}
}
}
test class is:
@isTest(SeeAllData=true)
public class CaseTrigger_Test {
Static testmethod void Test_trigger(){
Account acc=new Account();
acc.Name=’hemalathatest’;
acc.Device_Replacement__c=’Eligible’;
acc.Device_Replacement__c=’None’;
Insert acc;
case cas1=new case();
cas1.AccountId=acc.Id;
cas1.Origin=’Web’;
cas1.Status=’Working’;
cas1.Roku_Offer__c=’HDMI’;
Insert cas1;
case cas1=[select AccountId from case where AccountId=:acc.Id];
System.assertEquals( cas1.AccountId,acc.Name);
}
}
please check my test class and helped me thanks in advance
I am having a hard time to run my test class for my trigger WHERE my trigger updates the Contacts Mailing to Accounts Biliing Address whenever the Accounts Billing Address is updated.
This is my Trigger
trigger UpdateContactBillingAddress on Account (after update) {
Set accountId = new Set();
List contactsToUpdate = new List();
for(Account acc: Trigger.new){
// Please perform a check to see if address was updated
accountId.add(acc.Id); //accounts that were updated.
}
for(Contact c : [Select Id, MailingCity, MailingStreet, MailingCountry,
Account.BillingCity, Account.BillingStreet, Account.BillingCountry
from Contact where Account.Id in: accountId]){
c.MailingCity = c.Account.BillingCity;
///same for rest of the fields
contactsToUpdate.add(c);
}
update contactsToUpdate;
}
It works fine.But my test class is giving me errors: Compile Error: Initial term of field expression must be a concrete SObject: List at line 24 column 41
My Test Class
@isTest
public class UpdateContactBillingAddress_Test{
static testMethod void testupdates() {
// Let’s create our records from scratch!
Account a= new Account();
a.Name=’TestAccount’;
a.BillingStreet=’hjasadhj’;
insert a;
//update Account
a.billingState=’My City’;
update a;
// Verify that the billingState field was updated in the database.
Account updatedAccount = [SELECT billingState FROM Account WHERE Id = :a.Id];
System.assertEquals(‘My City’, updatedAccount.billingState);
//Create record for new contact
Contact c=new Contact();
c.FirstName=’hina’;
c.LastName=’meena’;
List contact = [SELECT Id, MailingStreet,MailingCity FROM Contact WHERE AccountId = :a.Id];
System.assertEquals(‘hjasadhj’, contact.MailingCity);
}
}
Can anyone help me find the error/debug it.
Thanks
Thanks David
Hi David,
I am trying the below test class and getting error System.DmlException: Insert failed. First exception on row 0; first error: INVALID_CROSS_REFERENCE_KEY, invalid cross reference id: []. Please assist
@isTest
public class TestFindDupes
{
static testMethod void testDupes()
{
Contact c = new Contact();
c.FirstName = ‘Stephen’;
c.LastName = ‘Curry’;
c.Email = ‘stephcurry@gsw.com’;
insert c;
User userToCreate = new User();
userToCreate.FirstName = ‘Stphn’;
userToCreate.LastName = ‘Curry’;
userToCreate.Email = ‘stephcurry@gsw.com’;
userToCreate.Username = ‘stephcurry@gsw.com’;
userToCreate.Alias = ‘fatty’;
userToCreate.ProfileId = ‘00590000002mN8G’;
userToCreate.TimeZoneSidKey = ‘America/Denver’;
userToCreate.LocaleSidKey = ‘en_US’;
userToCreate.EmailEncodingKey = ‘UTF-8’;
userToCreate.LanguageLocaleKey = ‘en_US’;
try{
insert userToCreate;
}
catch(Exception e)
{
System.debug(‘Error’);
}
List dp = [Select Id from User Where Email = ‘stephcurry@gsw.com’];
System.assertEquals(0,dp.size());
User u = new User();
u.FirstName = ‘Ram’;
u.LastName = ‘lakhan’;
u.Email = ‘rlak@hcl.com’;
u.Username = ‘rlak@hcl.com’;
u.Alias = ‘fatty’;
u.ProfileId = ‘00590000002mN8G’;
u.TimeZoneSidKey = ‘America/Denver’;
u.LocaleSidKey = ‘en_US’;
u.EmailEncodingKey = ‘UTF-8’;
u.LanguageLocaleKey = ‘en_US’;
insert u;
List lp = [Select Id from User Where Email = ‘rlak@hcl.com’];
System.assertEquals(1,lp.size());
}
}
Make sure this ID is a valid Profile ID in your org!
00590000002mN8G
Hello David,
I have created trigger to check duplicate contact and assign duplicate contact ID to custom lookup field on contact. Trigger is working fine but Test class code coverage is zero:(
Trigger:
@isTest
public class testfindDupcontact{
static void testDupContact(){
contact con= new contact();
con.firstName=’sonu’;
con.lastName=’Rajawat’;
con.email=’sonurajawat88@gmail.com’;
insert con;
contact con1=new contact();
con1.firstName=’Sattu’;
con1.lastName=’Rajawat’;
insert con1;
contact con2=new contact();
con2.firstName=’sonu’;
con2.lastName=’rajawat’;
con2.email=’sonurajawat88@yahoo.com’;
insert con2;
con2=[select id, potential_duplicate_contact__c from contact where firstname=:con2.firstname And lastName=:con2.lastName limit 1];
con1=[select id, potential_duplicate_contact__c from contact where firstname=:con1.firstname And lastName=:con1.lastName limit 1];
system.assertEquals(con.id, con2.potential_duplicate_contact__c);
system.assertEquals(null, con1.potential_duplicate_contact__C);
con2.firstName=’puneet’;
con2.lastName=’bhasin’;
update con2;
system.assertequals(null, con2.potential_duplicate_contact__c);
}
}
————————
Test class
@isTest
public class testfindDupcontact{
static void testDupContact(){
contact con= new contact();
con.firstName=’sonu’;
con.lastName=’Rajawat’;
con.email=’sonurajawat88@gmail.com’;
insert con;
contact con1=new contact();
con1.firstName=’Sattu’;
con1.lastName=’Rajawat’;
insert con1;
contact con2=new contact();
con2.firstName=’sonu’;
con2.lastName=’rajawat’;
con2.email=’sonurajawat88@yahoo.com’;
insert con2;
con2=[select id, potential_duplicate_contact__c from contact where firstname=:con2.firstname And lastName=:con2.lastName limit 1];
con1=[select id, potential_duplicate_contact__c from contact where firstname=:con1.firstname And lastName=:con1.lastName limit 1];
system.assertEquals(con.id, con2.potential_duplicate_contact__c);
system.assertEquals(null, con1.potential_duplicate_contact__C);
con2.firstName=’puneet’;
con2.lastName=’bhasin’;
update con2;
system.assertequals(null, con2.potential_duplicate_contact__c);
}
}
Trigger is:
trigger LeadDuplicate on Contact (before insert, before update) {
for (contact con: trigger.new){
list DupConList= [select Id from contact where firstname=:con.firstname And lastName=:con.lastname];
if(dupconlist.size()>0){
con.potential_duplicate_contact__c= dupconlist[0].ID;
}
else{
con.potential_duplicate_contact__c=null;
}
}
}
David,
I was trying to do something similar. This is a test class for a trigger that shows an error msg whenever you try to buy more tickets than that are available.
@istest
public class soldout{
static testmethod void soldouttickets(){
skall__films__c myfilm = new skall__films__c();
myfilm.Name = ‘Shrek’;—text
myfilm.skall__Available_Tickets__c = 20;—number
myfilm.skall__time__C = ‘1000’; —picklist
myfilm.skall__Date__c == 08/07/2014;–date field
insert myfilm;
skall__booking__c mybooking = new skall__booking__c() ;
mybooking.skall__title__c = myfilm.Name;—lookup
mybooking.skall__Date__c == 08/07/2014; —date field
mybooking.skall__time__C = ‘1000’;–picklist
mybooking.skall__number_of_tickets__c = 30;—number
try{
insert mybooking;
}
catch(Exception e ){
system.debug( ‘SOLD OUT’);
}
}
}
It shows error in the date comparisons. can you please help. Thanks.
It looks like your date fields aren’t being set in this example. Your using the equality operator (==) instead of assignment (=)
David,
What are the test methods available in isTest annotation???
These bad boys!
https://www.salesforce.com/us/developer/docs/apexcode/Content/apex_methods_system_test.htm
But I wouldn’t worry about most of these in the beginning.
Hey david,
In that dupes whether we can access all the fields in that Id(which is equal to email) or not??
which means we can do like this
lead l=new lead();
l.custom_contact__c=dupes[0].ContactName;
Thanks in advance,
Karthick
Hey Karthick,
We can only do this:
l.custom_contact__c = dupes[0].Name;
Only if we queried for the Name field!
List<Contact> dupes = [SELECT Id, Name FROM Contact];
Hye david,
I have some doubt in following code
List dupes = [SELECT Id FROM Lead
WHERE Email = ‘stephcurry@gsw.com’];
What Id represents??
what are the fields available in dupes list ??
Thanks in advance
Karthick
Id represents the fields of the leads we want to query.
We could’ve also done Id, Name, Email, CreatedDate etc etc etc
In that dupes whether we can access all the fields in that Id(which is equal to email) or not??
which means we can do like this
lead l=new lead();
l.custom_contact__c=dupes[0].ContactName;
Thanks in advance,
Karthick
hello david,
What is the purpose of annotation and without annotation in test class what will happen(Which means it will wort or not)
Thanks in advance
Karthick
You have limits to the amount of code you can write in Salesforce!
That annotation tells Salesforce to ignore those lines of codes when calculating the limits (among other things)
Is there a good place to learn more about the”fluff”, i.e. this part “static testMethod void testDupes() {”
What specifically does it mean?
This book will help you with that side of things:
https://www.sfdc99.com/2013/05/20/the-best-way-to-quickly-learn-how-to-code-in-salesforce/
You can also Google the terms and official Apex documentation will explain in more detail =)
I chose not to explain those in the earlier chapters (but I will in later ones) because the topics are too advanced for the scope of work in the beginning. Plus, other places explain them in better detail!
Hi David,
Thanks for detail information to write test class with nice images to remember. :-)
I have below question regarding above test class:
In test class, we are inserting lead as below:
// This is a special way of doing “risky” things
try {
insert dupeLead;
} catch (Exception e) {
System.debug(‘An error happened, as predicted!’);
}
// Now we try to find our dupe lead, by email
List dupes = [SELECT Id FROM Lead
WHERE Email = ‘stephcurry@gsw.com’];
System.assertEquals(0, dupes.size());
and then querying Lead which has email id as ‘stephcurry@gsw.com’. So, Won’t it give one record in List?
If it will give one record then won’t it fail assertEqual() here?
Please solve my confusion over here.
Thank you,
Amit
Hello Amit,
When you first tried to insert the dupeLead, it would give you an error since a contact with the same email already exists. Hence the lead would not be created. So the size of the list would be 0.
Hope this helps.
SKall
SKall nailed it – you’re 100% right!
Thank you SKall!
David
Thank you very much Skall and David! :-)
I came across methods like Test.start test() and Test .stoptest()in Test classes. what do they mean and where exacty in code we need to use it? In few examples its written above the DML statements and in other case, Its been covered with entire code. Please clarify this.
Hey Neha,
Sometimes when writing test classes, you need to create a lot of records to fully test your code! In these cases, it’s common to run into Governor Limits:
https://www.sfdc99.com/2013/11/17/what-are-governor-limits/
Test.startTest() resets your Governor Limits – it’s only available while testing but it is very helpful!! I will have a post on this in the future!
David
I am just learning to write testclasses.
This i have tried for the”MAP TRAINING’ class in ur org.kindly validate it.
Public class TestMapTraining{
static test method void map(){
Account a= new Account();
a.name=’012′;
a.email=’neha.gg@gmail.com’;
insert a;
MapTraining mt= new MapTraining();
a.name=’1234′;
update a;
mt.updateaccounts(a.id,a.name);
String name=mt.lst.get(‘id’); —-hard coded id
system.assertequals(1234,name);
}
}
Or I should write in this way??
Public class TestMapTraining{
static test method void map(){
Account a= new Account();
a.name=’012′;
a.email=’neha.gg@gmail.com’;
insert a;
Map m= new Map();
m.put(a.id, a);
MapTraining mt= new MapTraining();
mt.updateaccounts(m);
String name=mt.lst.get(‘id’); —-hard coded id
system.assertequals(1234,name);
}
}
Hey Neha,
I made a few modifications – try this out instead! Comments are in line:
@IsTest
public class TestMapTraining{
static testmethod void maptrainingtest(){
// Create an account
Account a = new Account();
a.name = ‘012’;
insert a;
// Create a map accountMap = new Map();
Map
accountMap.put(a.Id, a);
// Test the MapTraining
MapTraining mt= new MapTraining();
mt.updateaccounts(accountMap);
// Check if the name of the account was updated correctly
Account updatedAccount = [SELECT Id, Name FROM Account WHERE Id = :a.Id];
system.assertequals(‘1234’, updatedAccount.Name);
}
}
David
Yes, Thank u …
My mistakes.
1) forgot @ISTEST
2) i have used reserved keyword “map” as test method name
3)need to practice more on queries too..
Thanks
neha