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
- use
- 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, butTest.startTest()
andTest.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 loadcsv
files fromstatic 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 anHttpRequest
and returns anHttpResponse
. - 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 implementsWebServiceMock
interface, based on classes generated byWSDL2Apex
.- 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:- Create test class
- Create
PageReference
toVisualforce
page - Pass
PageReference
variable toTest.setCurrentPage()
to set currentPageReference
- Instantiate the controller for the page
- Instantiate an extension for the page by passing the controller variable as parameter to the extension constructor (if needed)
- Pass URL parameters to the controller (if needed)
ApexPages.currentPage.getParameters().put('paramName', 'paramValue');
- Use the controller's setter method to simulate user input for data fields (if needed)
- 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 resultingPageReference
variable. -
NOTE: using
getParameters()
will return URL parameter but it is not a complete one.getUrl()
must be used to return the exact URL asPageReference
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
withcontroller 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 aSOSL
query. It can be called multiple times within a single test method to change what data is returned each time.Test.startTest()
andTest.stopTest()
can be used to get a fresh set of governor limits when dealing with large data sets.- NOTE:
Test.startTest()
andTest.stopTest()
can ensure all asynchronous calls that come afterstartTest
method are run before any assertions or testing. ApexTestQueueItem
andApexTestResult
allow tests to be run asynchronously at specific time usingApex Scheduler
.- Tests can be added to
Apex Job Queue
usingApexTestQueueItem
and the result can be checked inApexTestResult
. 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 inSetup > 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
- 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
.
- You can also set 'number of failures allowed' and 'skip code coverage' in
- 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 inDeveloper Console > Test > New Suite
.Test Suite
can be run inDeveloper Console > Test > New Suite Run
.Test Suite
can be managed inDeveloper Console > Test > Suite Manager
.Test Suite Manager
can create New Suite, Rename Suite, Edit Suite, and Delete Suite.
Code coverage
can be skipped fromApex Text Execution
page and fromDeveloper 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 Tool | Available 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 API | Run 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!