Most Salesforce developers, at some point, will work on a trigger that has grown to have many different responsibilities. When this happens, keeping all of your Apex tests in one test class can become unmanageable. It might be necessary to split those tests into multiple different test classes. But how can you ensure that all tests continue to pass after a change is made to your trigger? Test suites, introduced in the Spring ’16 release, make managing your test classes a piece of cake.
Suppose we wish to build a trigger on the Opportunity object. This trigger will check if an opportunity of type “New Customer” has been inserted into the database. If it has, the trigger will create a second opportunity with a close date one year after the original, marking the Type field as “Existing Customer – Upgrade”.
Using a trigger framework, we first build the trigger code:
trigger OpportunityTrigger on Opportunity (before insert, before delete){
if(trigger.isInsert){
new OpportunityTriggerHandler().beforeInsert(trigger.new);
}
else if(trigger.isDelete){
new OpportunityTriggerHandler().beforeDelete(trigger.old);
}
}
The trigger handler will have two functions: beforeInsert and beforeDelete. In the beforeInsert function, we will first check for any opportunities that are of type “New Customer” and create an upgrade Opportunity for each one. In the beforeDelete function, we will check if any of the “New Customer” opportunities are being deleted, and remove all existing Upgrade opportunities on their accounts.
public class OpportunityTriggerHandler {
public void beforeInsert(List<Opportunity> oppList){
List<Opportunity> upgradeOppList = new List<Opportunity>();
for(Opportunity opp : oppList){
if(opp.Type == 'New Customer'){
Opportunity upgradeOpp = new Opportunity(Name = opp.Name,
Type= 'Existing Customer - Upgrade',
Amount = opp.Amount,
StageName = opp.StageName,
CloseDate = opp.closeDate.addYears(1),
AccountID = opp.accountID);
upgradeOppList.add(upgradeOpp);
}
}
insert upgradeOppList;
}
public void beforeDelete(List<Opportunity> oppList){
Set<ID> accountIDs = new Set<ID>();
for(Opportunity opp : oppList){
if(opp.Type == 'New Customer'){
accountIds.add(opp.accountID);
}
}
List<Opportunity> upgradeOpps = [SELECT ID FROM Opportunity
WHERE accountID IN:accountIds
AND Type ='Existing Customer - Upgrade'];
delete upgradeOpps;
}
}
We will need to test the trigger’s behavior using some test classes. Creating a separate test class for the Before Insert and Before Delete logic can help us better organize our tests.
Here is the code for the Before Insert test class:
@isTest
public class TestOpportunityBeforeInsert {
@testSetup
private static void testSetup(){
Account acc = new Account(Name = 'Morris and Ross');
insert acc;
}
//when a new opportunity is created that is of type 'New Customer'
//verify that a second opportunity of type 'Existing Customer - Upgrade' is created
@isTest
private static void testCreateExistingCustomerOpp(){
Account acc = [SELECT ID FROM Account LIMIT 1];
Opportunity newOpp = new Opportunity(Name = 'newOpp',
Type = 'New Customer',
stageName='Prospecting',
CloseDate=Date.Today(),
AccountID = acc.id);
insert newOpp;
//check that an Existing Customer - Upgrade opportunity was created
Opportunity newExistingCustomerOpp = [SELECT ID, Type FROM Opportunity
WHERE Type='Existing Customer - Upgrade'
AND accountID =: acc.id];
System.assertEquals('Existing Customer - Upgrade',newExistingCustomerOpp.Type);
}
//verify that when the opportunity type is not 'New Customer'
//no other opportunity is created
@isTest
private static void testNoExistingCustomerOppCreated(){
Account acc = [SELECT ID FROM Account LIMIT 1];
Opportunity existingOpp = new Opportunity(Name = 'newOpp',Type = 'Existing Customer - Upgrade',
stageName='Prospecting',
CloseDate=Date.Today(),
AccountID = acc.id);
insert existingOpp;
//check that a second opportunity was not created
List<Opportunity> oppList = [SELECT ID, Type FROM Opportunity];
System.assertEquals(1,oppList.size());
}
}
The Test class for the Delete Trigger is shown below:
@isTest
public class TestOpportunityBeforeDelete {
@testSetup
private static void testSetup(){
Account acc = new Account(Name = 'Morris and Ross');
insert acc;
}
@isTest
private static void testDeleteUpgrade(){
Account acc = [SELECT ID FROM Account LIMIT 1];
Opportunity newOpp = new Opportunity(Name = 'newOpp',
Type = 'New Customer',
stageName='Prospecting',
CloseDate=Date.Today(),
AccountID = acc.id);
insert newOpp;
List<Opportunity> oppList = [SELECT ID FROM Opportunity];
System.assertEquals(2,oppList.size());
delete newOpp;
oppList = [SELECT ID FROM Opportunity];
System.assertEquals(0,oppList.size());
}
@isTest
static void testDoNotDeleteNewCustomerOpp(){
Account acc = [SELECT ID FROM Account LIMIT 1];
Opportunity newOpp = new Opportunity(Name = 'newOpp',
Type = 'New Customer',
stageName='Prospecting',
CloseDate=Date.Today(),
AccountID = acc.id);
insert newOpp;
//delete the existing customer opportunity
Opportunity opp = [SELECT ID FROM Opportunity
WHERE Type='Existing Customer - Upgrade'
AND accountID=:acc.id];
delete opp;
List<Opportunity> opportunityList = [SELECT ID FROM Opportunity WHERE AccountID=:acc.id];
System.assertEquals(1,opportunityList.size());
}
}
We have built two separate test classes for our Opportunity trigger and separated the tests according to the type of logic they are testing. However, since all of these tests are verifying the logic in the Opportunity trigger, we want to run both test classes at the same time. We can build a test suite for the trigger, and add both test classes so that they can be run simultaneously with minimal effort. This can be done from the developer console.
To build a test suite, open the Developer Console, and under Test, select New Suite:
Give your suite a unique name and add all of the tests classes you wish to include in your suite:
Once you’ve saved your new test suite, navigate to Test, then New Suite Run. Running your test suite should allow you to see the results of all tests contained in that suite, with the convenience of only a few clicks!
Why go through all the trouble to build test suites?
Placing tests into separate, smaller test classes allows them to be organized by the functionality they are testing. If we placed all of our tests into one file, it might take too long to run. Shortening test execution time is important in Test Driven Development, where the tests need to be continuously run as the code is being written. Test Suites give developers the option to write smaller test classes and keep integration testing fast by running all relevant tests at once. There’s also the benefit of not having to deploy any code that isn’t production ready if the tests for that code are in a separate test class. Now that Test Suites have been delivered, it will be interesting to see what other Salesforce development enhancements are on the horizon.
Next Steps
Have questions? Let us know in a comment below, or contact our team directly. You can also check out our other posts on customizing your Salesforce solution.