Testing consists of 12% of total score in Salesforce Platform Dev II Exam. The topic covers including Unit Testing, testing best practices, testing techniques, testing execution and etc.

NOTE: This post is written in July 2019 and content might be changed/updated overtime. The content is inspired by focusonforce.com.

Apex Testing Best Practices

  • Unit testing best practices:
    • use @isTest annotation for every test class (test utility class, test data factory, and etc.)
    • execute every single line of code and expect errors to be caught
    • use System.assert() to make sure code is executed as expected
    • use System.runAs() to test the code in different user contexts.
    • make sure tests are written to handle bulk operations
    • each class should be tested individually
  • Test data setup best practices:
    • test data should be used to reduce dependencies on data in sandbox and production.
    • separate test classes should be used to setup necessary data
    • test data should be created before calling Test.startTest method.
    • Test data does not need to be deleted because it is not committed.
  • Test coverage best practices:
    • 75% of code must be covered by unit tests
    • managed package tests are not included in overall code coverage percentage
  • Parallel test execution best practices:
    • tests executed from Salesforce user interface and Developer Console are executed in parallel.
    • parallel test execution can be turned off by checking 'Disable Parallel Apex Testing' in Setup > Apex Test Execution.
    • if own test data is not created, tests may update the same records during parallel test execution.
  • NOTE: Method of test utility class does not necessarily take no parameters or ni returns, test utility method can take parameters and return a value.
  • Code in test utility class will be excluded from organization code size limit if it is marked with @isTest.
  • NOTE: System.runAs() can be used to separate transaction, but Test.startTest() and Test.stopTest() cannot.

Apex Test Setup

  • Test setup methods are defined in a test class with no argument, no return value, and is annotated by @testSetup.
  • Test setup methods run automatically before any tests are started.
  • Test setup methods are not supported in a test class that is annotated with @isTest (SeeAllData=True).
  • NOTE: only one @testSetup method can be defined in a class.
  • Test utility class can be created to create test records (usually called test data factory)
  • Test data can also be loaded into test by using Test.loadData() method to load csv files from static resource.
  • NOTE: one file should be used for one object type and the first line of the file must contain object's field API name.
  • Example of loading test data from static resource:
List<sObject> accounts = Test.loadData(Account.sObjectType, 'testData');
System.assert(accounts.size() == 3);
System.assertEquals('Test', ((Account) accounts[1]).Name);
  • NOTE: You can choose to run all tests in Setup > Apex Class.
  • NOTE: If a test class is annotated with @isTest (SeeAllData=true) annotation, annotating any test method of the class with @isTest (SeeAllData=false) will not override @isTest (SeeAllData=true) in test class. If a test method is annotated with @isTest (SeeAllData=true), then annotating @isTest (SeeAllData=false) in test class will not override test method.

Callout Mock Response

HTTP Callout Mock Response

  • HTTP callouts can be tested by setting mock class that implements HttpCalloutMock interface.
  • The class must be either global or public and @isTest annotation should be used on the class since it will only be used in test context.
  • Only respond() method needs to be implemented which accepts an HttpRequest and returns an HttpResponse.
  • Callout mock response best practices:
    • separate class should be created for each test callout
  • In the main unit test, Test.setMock needs to be set for receiving fake response from mocking callout class
  • Example: Test.setMock(HttpCalloutMock.class, new CalloutMockClass());

SOAP Callout Mock Response

  • SOAP callouts can be tested by setting mock class that implements WebServiceMock interface, based on classes generated by WSDL2Apex.
  • The class must be either global or public and @isTest annotation should be used on the class since it will only be used in test context.
  • Only doInvoke() method needs to be implemented which all information above the callout, including the response, is passed through multiple input parameters.
  • Example: Test.setMock(WebServiceMock.class, new CalloutMockWebServiceClass());

Testing Controllers & Controller Extensions

  • Steps to setup test of Visualforce controller or extension:

    1. Create test class
    2. Create PageReference to Visualforce page
    3. Pass PageReference variable to Test.setCurrentPage()to set current PageReference
    4. Instantiate the controller for the page
    5. Instantiate an extension for the page by passing the controller variable as parameter to the extension constructor (if needed)
    6. Pass URL parameters to the controller (if needed)
      • ApexPages.currentPage.getParameters().put('paramName', 'paramValue');
    7. Use the controller's setter method to simulate user input for data fields (if needed)
    8. Call controller's action methods to simulate a user clicking a button
  • NOTE: a test class cannot be named as 'Test' (will throw error if doing so)

  • If the controller redirects to another page based on user input, the resulting page name can be checked by calling the getUrl() method of the resulting PageReference variable.

  • NOTE: using getParameters() will return URL parameter but it is not a complete one. getUrl() must be used to return the exact URL as PageReference variable.

  • Example 1 of testing custom controller:

<!-- visualforce page named "AccPage" -->
<apex:page controller="CustomController" tabStyle="Account">
    <apex:form>
        <apex:pageBlock title="Account Page">
            <apex:pageBlockSection>
                <apex:inputField value="{!acc.Name}" />
                <apex:inputField value="{!acc.Type}" />
                <apex:commandButton action="{!save}" value="save" />
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>
// custom controller class
public class CustomController{
    public Account acc;
    public CustomController(){
        acc = [SELECT Id, Type FROM Account WHERE Id = :ApexPages.currentPage().getParameters().get('id')];
    }
    
    public Account getAcc(){
        return acc;
    }
    
    public PageReference save(){
        update acc;
        return null;
    }
}
// custom controller test class 
@isTest
public class CustomControllerTest{
    @isTest
    public static void testMethod(){
        Account acc = new Account(
            Name = 'Test'     
        );
        acc.Type = 'Customer';
        insert acc;
        
        PageReference testPage = Page.AccPage;
        
        // set current page reference
        Test.setCurrentPage(testPage);
        
        // set query parameter
        ApexPages.currentPage().getParameters().put('id', a.Id);
        
        // instantiate custom controller
        CustomController cc = new CustomController();
        
        // call custom controller
        PageReference pr = cc.save();
        cc.getAcc();
    }
}
  • Example 2 of testing standard controller with controller extension:
<!-- visualforce page named "CustomAccPage" -->
<apex:page standardController="Account" extensions="CustomControllerExtension" tabStyle="Account">
    <apex:form>
        <apex:pageBlock title="Account Page">
            <apex:pageBlockSection>
                <apex:outputText value="{!details}" /> <p/>
                <apex:outputField rendered="false" value="{!acc.Type}" />
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>
// custom controller extension class
public class CustomControllerExtension{
    public Account acc;
    public CustomControllerExtension(ApexPages.StandardController stdController){
        acc = (Account) stdController.getRecord();
    }
    
    public String getDetails(){
        return 'Account name is ' + acc.Name + ' and type is ' + acc.Type;
    }
}
// custom controller test class 
@isTest
public class CustomControllerExtensionTest{
    @isTest
    public static void testMethod(){
        Account acc = new Account(
            Name = 'Test'     
        );
        
        acc.Type = 'Customer';
        insert acc;
        
        PageReference testPage = Page.CustomAccPage;
        
        // set current page reference
        Test.setCurrentPage(testPage);
        
        // set query parameter
        ApexPages.currentPage().getParameters().put('id', a.Id);
        
        // instantiate standard controller
        ApexPages.StandardController sc = new ApexPages.StandardController(acc);
        
        CustomControllerExtension cce = new CustomControllerExtension(sc);
        
        System.debug(cce.getDetails());
    }
}

Apex Test Execution Options

  • @TestVisible annotation can be used on any private or protected variable, method or inner class to make it visible to unit test class.
  • Options for @isTest annotation:
    • @isTest(SeeAllData=true) - allow test class to see all records in org
    • @isTest(isParallel=true) - allow test class to run in parallel (override the default setting and not restricted by default limits of concurrent tests)
    • @isTest(OnInstall=true) - allow specifying which test class are executed during package installation
  • Test.setFixedSearchResults() method creates a defined set of record Ids to be returned any time a test method executes a SOSL query. It can be called multiple times within a single test method to change what data is returned each time.
  • Test.startTest() and Test.stopTest() can be used to get a fresh set of governor limits when dealing with large data sets.
  • NOTE: Test.startTest() and Test.stopTest() can ensure all asynchronous calls that come after startTest method are run before any assertions or testing.
  • ApexTestQueueItem and ApexTestResult allow tests to be run asynchronously at specific time using Apex Scheduler.
  • Tests can be added to Apex Job Queue using ApexTestQueueItem and the result can be checked in ApexTestResult.
  • System.runAs() can be used to specify a block of code that should be run as the user.
User u = [SELECT Id, Name FROM User WHERE Name = 'Test'];
System.runAs(u){
    System.assertEquals(UserInfo.getName(), u.Name);
}
  • Apex Test Execution options can be set in Setup > Apex Text Execution:
    • Store Only Aggregated Code Coverage (reduce code coverage calculation time when executing many tests on large volume of code)
    • Disable Parallel Apex Testing
    • Independent Auto-Number Sequence
      apex-text-execution-options
  • New test run can be set in Developer Console > Test > New Run:
    • You can also set 'number of failures allowed' and 'skip code coverage' in Settings.
      run-test-settings
  • You can set the test to always run asynchronously by checking 'Always Run Asynchronously' in Developer Console > Test > Always Run Asynchronously.
  • Test suite can be created if group of tests needed to run together frequently, saving time of selecting each test manually.
    • Test Suite can be created in Developer Console > Test > New Suite.
    • Test Suite can be run in Developer Console > Test > New Suite Run.
    • Test Suite can be managed in Developer Console > Test > Suite Manager.
      • Test Suite Manager can create New Suite, Rename Suite, Edit Suite, and Delete Suite.
        suite-manager-options
  • Code coverage can be skipped from Apex Text Execution page and from Developer Console in settings after selecting tests to run, this option is helpful if developers want faster feedback on execution result status rather than code coverage.
  • Other testing tools and available options:
Testing ToolAvailable Options
Apex Text Execution from Setup- Store Only Aggregated Code Coverage
- Disable Parallel Apex Testing
- Independent Auto-Number Sequence
Developer Console- Always run asynchronously
- Number of failures allowed
- Create/run test suite
Force.com IDE- Create/run test suite
SOAP APIRun test synchronously using runTests() from SOAP API
Tooling REST API- Run test synchronously and asynchronously by calling the following endpoints:
/runTestsAsynchronous/
/runTestsSynchronous/

Well, that's it! Hope this content is enough to cover all the testing chapter. Have a great one!

Post was published on , last updated on .

Like the content? Support the author by paypal.me!