Arrange Your Testing Classes Using Test Suites.

At some point in their Salesforce development journey, most developers will encounter triggers that accumulate diverse responsibilities. As the trigger grows, maintaining all Apex tests within a single test class can become challenging. It may be necessary to distribute these tests across multiple classes. The question then arises: How can you ensure that all tests remain successful following modifications to your trigger? Test suites, introduced in the Spring ’16 release, simplify the management of your test classes.

Consider the scenario where you want to create a trigger on the Opportunity object. This trigger is designed to verify whether an opportunity of the “New Customer” type has been inserted into the database. If so, the trigger will generate a second opportunity with a close date set one year after the original, and it will label the Type field as “Existing Customer – Upgrade.”

When employing a trigger framework, the initial step involves constructing 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 consist of two functions: beforeInsert and beforeDelete. In the beforeInsert function, the initial step involves examining opportunities with the “New Customer” type and generating an upgrade Opportunity for each identified case. Meanwhile, in the beforeDelete function, a check is performed to ascertain if any “New Customer” opportunities are being deleted. If so, all existing Upgrade opportunities associated with their respective accounts will be removed.

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;
     }
}

To validate the trigger’s functionality, we must conduct testing using specific test classes. Establishing distinct test classes for the Before Insert and Before Delete logic enhances the organization of our tests.

Below 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());
     }
}

Two distinct test classes have been constructed for our Opportunity trigger, categorizing the tests based on the specific logic they evaluate. Despite the segregation of tests, as they all validate the logic within the Opportunity trigger, the intention is to execute both test classes simultaneously. To achieve this, we can create a test suite for the trigger and include both test classes, streamlining the process of running them concurrently. This can be accomplished through the developer console.

To create a test suite, navigate to the Developer Console, and under the “Test” category, choose “New Suite.”

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:

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.

What’s the motivation behind the effort to construct test suites?

Organizing tests into distinct, smaller test classes facilitates categorization based on the specific functionality they evaluate. If all tests were consolidated into a single file, the execution time might become prolonged. In Test-Driven Development, where tests need to be consistently run during code creation, minimizing test execution time is crucial. Test Suites provide developers with the flexibility to create smaller test classes, ensuring the swift execution of integration tests by running all relevant tests simultaneously. Another advantage is the avoidance of deploying any code that isn’t production-ready, as long as the tests for that code are located in a separate test class. With the introduction of Test Suites, it will be intriguing to observe what other enhancements are in store for Salesforce development.