Salesforce Unit Test Best Practices

Testing is the key to successful long-term development and is a critical component of the development process. Unit tests validate that your application works as expected, that there are no unexpected behaviors.

Apex provides a testing framework that allows you to write unit tests, run your tests, check test results, and have code coverage results.

Unit tests are class methods that verify whether a particular piece of code is working properly. Unit test methods take no arguments, commit no data to the database, and send no emails. Such methods are flagged with the @isTest annotation in the method definition. Unit test methods must be defined in test classes, that is, classes annotated with @isTest.

Test methods and test classes are not counted as a part of the Code Limit. The default access modifier of test class and method is private, no matter to add a specific access modifier.

Unit Test Logical Parts

  • Preparation a test data – you need to use test data and setting to run the code. If you will use data in the Salesforce Organization you will have problems during deployment because data can be missed or different
  • Mocks and Stubs setup – if you need to mock some methods you will need to prepare it before the execution
  • Method execution
  • Assert the results – it is an important part to check the test results. You can check the method returned object or query some records from the database, that verified the method created or updated

Write Unit Tests to check the logic, not to Increase Code Coverage

This is the most important best practice because tests are a way to check that you implemented the right logic.

Write tests to different scenarios

  • Single action – to verify that a single record produces the correct, expected result
  • Bulk actions – any Apex code, whether a trigger, a class, or an extension, may be invoked for 1 to 200 records
  • Positive behavior – test to verify that the expected behavior occurs through every expected permutation if the user filled out everything correctly and did not go past the limits
  • Negative behavior – there are likely limits to your applications, such as not being able to add a future date, not being able to specify a negative amount, and so on. You must test for the negative case and verify that the error messages are correctly produced as well as for the positive, within the limits cases
  • Restricted user – test whether a user with restricted access to the sObjects used in your code sees the expected behavior. That is, whether they can run the code or receive error messages.

Use @testSetup to prepare a data

Use test setup methods to create test records once and then access them in every test method in the test class. Test setup methods can be time-saving when you need to create a reference or prerequisite data for all test methods, or a common set of records that all test methods operate on. Test setup methods can reduce test execution times especially when you’re working with many records. If a test class contains a test setup method, the testing framework executes the test setup method first, before any test method in the class. Records that are created in a test setup method are available to all test methods in the test class and are rolled back at the end of test class execution.

Use TestDataFactory class

TestDataFactory is a common test utility class. It contains reusable code for test data creation.

We are like to use the Builder pattern and Fluent API because it helps to set up an object in an agile way.

public with sharing class TestDataFactory {

    public static AccountBuilder account() {
        return new AccountBuilder();
    }

    public with sharing class AccountBuilder {
        private Account thisAccount = new Account();
        private AccountBuilder() {
            thisAccount.FirstName = 'Person';
            thisAccount.LastName = 'TestLastName';
            thisAccount.PersonEmail = '[email protected]';
            thisAccount.PersonMobilePhone = '123456789';
            return this;
        }
        public AccountBuilder setNationalId(String nationalId) {
            this.thisAccount.NationalId__pc = nationalId;
            return this;
        }
        public AccountBuilder setRecordType(final String recordTypeId) {
            this.thisAccount.RecordTypeId = recordTypeId;
            return this;
        }
        public Account build() {
            return this.thisAccount;
        }
    }
}

Example of the usage:

Account testAccount = TestDataFactory.account().setNationalId(1).build();

Use Mock and Stub objects

A stub is an object that holds predefined data and uses it to answer calls during tests. It is used when we cannot or don’t want to involve objects that would answer with real data or have undesirable side effects.

Mock is an object that registers calls they receive. In the test assertion, we can verify on Mocks that all expected actions were performed.

There are two way how we can implement it in Salesforce:

  • use ServiceLocator class to mock your methods, we already told about it early and discussed the advantages and disadvantages
  • use Salesforce Stub API. Apex provides a stub API for implementing a mocking framework. A mocking framework has many benefits. It can streamline and improve testing and help you create faster, more reliable tests. You can use it to test classes in isolation, which is important for unit testing.

Use the mock object is a big topic. We will cover it in some next posts.

Use Test.startTest() and Test.stopTest() methods

StartTest and stopTest methods help to validate how close the code is to reaching governor limits.

The startTest method marks the point in your test code when your test actually begins. All of the code before this method should be used to initialize variables, populate data structures, and so on. Any code that executes after the call to startTest and before stopTest is assigned a new set of governor limits. The startTest method does not refresh the context of the test: it adds a context to your test.

The stopTest method marks the point in your test code when your test ends. Any code that executes after the stopTest method is assigned the original limits that were in effect before startTest was called. All asynchronous calls made after the startTest method are collected by the system. When stopTest is executed, all asynchronous processes are run synchronously.

Use System.runAs to check the code under another user

All Apex code runs in system mode, where the permissions and record sharing of the current user are not taken into account. The system method runAs enables you to write test methods that change the user context to an existing user or a new user so that the user’s record sharing is enforced. The runAs method doesn’t enforce user permissions or field-level permissions, only record sharing.

You can use runAs only in test methods. The original system context is started again after all runAs test methods complete. The runAs method ignores user license limits. You can create new users with runAs even if your organization has no additional user licenses.

Don’t use SeeAllData = true

Test classes or test methods with annotation IsTest(SeeAllData=true have the access to records in your organization. It is bad practice because in this case your tests aren’t isolated from outside. It can’t have to predict behavior because data can be changed. Also, you can have problems during deployment because data can be missed or different.

Don’t use @Testvisible and Test.isRunningTest()

You write a unit test to check the code behavior. You will not need to modify your code to write unit tests for it. It is absolutely incorrect.

If you can’t write a test for the part of the code that can’t be executed in the unit test, like web services callout or send an email you need to mock this method.

If you have any questions or problems related to Salesforce Development – let us know. We always glad to help you.