Organize Your Test Classes with Test Suites

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:

Screenshot of creating a new Test Suite on the Test tab in the Developer Console
Fig 1 – We can create a new Test Suite from the Test tab in the Developer Console

Give your suite a unique name and add all of the tests classes you wish to include in your suite:

Suite Creation
Fig 2 – Selecting tests to be included in the Test 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!

Suite run
Fig 3 – Running the test suite will display the results of each individual test in the suite.

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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top