Apex Testing: Tips for Writing Robust Salesforce Test Methods
Improve your Apex testing skills with strategies for handling test data, managing governor limits, and manipulating time-based logic.
Join the DZone community and get the full member experience.
Join For FreeTesting is a critical aspect of software development. When developers write code to meet specified requirements, it’s equally important to write unit tests that validate the code against those requirements to ensure its quality. Additionally, Salesforce mandates a minimum of 75% code coverage for all Apex code.
However, a skilled developer goes beyond meeting this Salesforce requirement by writing comprehensive unit tests that also validate the business-defined acceptance criteria of the application.
In this article, we’ll explore common Apex test use cases and highlight best practices to follow when writing test classes.
Test Data Setup
The Apex test class has no visibility to the real data, so you must create test data. While the Apex test class can access a few set-up data, such as user and profile data, the majority of the test data need to be manually set up. There are several ways to set up test data in Apex.
1. Loading Test Data
You can load test data from CSV files into Salesforce, which can then be used in your Apex test classes. To load the test data:
- Create a CSV file with column names and values
- Create a static resource for this file
- Call
Test.loadData()
in your test method, passing two parameters — the sObject type you want to load data for and name of the static resource.
list<Sobject> los = Test.loadData (Lead.SobjectType, 'staticresourcename');
2. Using TestSetup Methods
You can use a method with annotation @testSetup
in your test class to create test records once. These records will be accessible to all test methods in the test class. This reduces the need to create test records for each test method and speeds up test execution, especially when there are dependencies on a large number of records.
Please note that even if test methods modify the data, they will each receive the original, unmodified test data.
@testSetup
static void makeData() {
Lead leadObj1 = TestDataFactory.createLead();
leadObj1.LastName = 'TestLeadL1';
insert leadObj1;
}
@isTest
public static void webLeadtest() {
Lead leadObj = [SELECT Id FROM Lead WHERE LastName = 'TestLeadL1' LIMIT 1];
//write your code for test
}
3. Using Test Utility Classes
You can define common test utility classes that create frequently used data and share them across multiple test classes.
These classes are public and use @IsTest
annotation. These utility classes can only be used in a test apex context.
@isTest
Public class TestDataFactory {
public static Lead createLead() {
// lead data creation
}
}
System Mode vs. User Mode
Apex test runs in system mode, meaning that the user permission and record sharing rules are not considered. However, it's essential to test your application under specific user contexts to ensure that user-specific requirements are properly covered. In such cases, you can use System.runAs()
. You can either create a user record or find a user from the environment, then execute your test code within that user’s context.
It is important to note that the runAs()
method doesn’t enforce user permissions or field-level permissions; only record sharing is enforced. Once the runAs()
block completes, the code goes back to system mode.
Additionally, you can use nested runAs()
methods in your test to simulate multiple user contexts within the same test.
System.runAs(User){
// test code
}
Test.startTest() and Test.stopTest()
When your test involves a large number of DML statements and queries to set up the data, you risk hitting the governor limit in the same context. Salesforce provides two methods — Test.startTest()
and Test.stopTest()
— to reset the governor limits during test execution.
These methods mark the beginning and end of a test execution. The code before Test.startTest()
should be reserved for set-up purposes, such as initializing variables and populating data structures. The code between these two methods runs with fresh governor limits, giving you more flexibility in your test.
Another common use case of these two methods is to test asynchronous methods such as future methods. To capture the result of a future method, you should have the code that calls the future between the Test.startTest()
and Test.stopTest()
methods. Asynchronous operations are completed as soon as Test.stopTest()
is called, allowing you to test their results.
Please note that Test.startTest()
and Test.stopTest()
can be called only once in a test method.
Lead leadObj = [SELECT Id FROM Lead WHERE LastName = 'TestLeadL1' LIMIT 1];
leadObj.Status = 'Contacted';
Test.startTest(); // start a fresh list of governor limits
//test lead trigger, which has a future callout that creates api logs
update leadObj;
Test.stopTest();
list<Logs__c> logs = [select id from Logs__c limit 1];
system.assert(logs.size()!= 0);
Test.isRunningTest()
Sometimes, you may need to write code to execute only in the test context. In these cases, you can use Test.isRunningTest()
to check if the code is currently running as part of a test execution.
A common scenario for Test.isRunningTest()
is when testing http callouts. It enables you to mock a response, ensuring the necessary coverage without making actual callouts. Another use case is to bypass any DMLs (such as error logs) to improve test class performance.
public static HttpResponse calloutMethod(String endPoint) {
Http httpReq = new Http();
HttpRequest req = new HttpRequest();
HttpResponse response = new HTTPResponse();
req.setEndpoint(endPoint);
req.setMethod('POST');
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setTimeout(20000);
if (Test.isRunningTest() && (mock != null)) {
response = mock.respond(req);
} else {
response = httpReq.send(req);
}
}
Testing With TimeStamps
It's common in test scenarios to need control over timestamps, such as manipulating the CreatedDate
of a record or setting the current time, in order to test application behavior based on specific timestamps.
For example, you might need to test a UI component that loads only yesterday’s open lead records or validate logic that depends on non-working hours or holidays. In such cases, you’ll need to create test records with a CreatedDate
of yesterday or adjust holiday/non-working hour logic within your test context.
Here’s how to do that.
1. Setting CreatedDate
Salesforce provides a Test.setCreatedDate
method, which allows you to set the createddate
of a record. This method takes the record Id
and the desired dateTime
timestamp.
@isTest
static void testDisplayYesterdayLead() {
Lead leadObj = TestDataFactory.createLead();
Datetime yesterday = Datetime.now().addDays(-1);
Test.setCreatedDate(leadObj.Id, yesterday);
Test.startTest();
//test your code
Test.stopTest();
}
2. Manipulate System Time
You may need to manipulate the system time in your tests to ensure your code behaves as expected, regardless of when the test is executed. This is particularly useful for testing time-dependent logic. To achieve this:
- Create a getter and setter for a
now
variable in a utility class and use it in your test methods to control the current time.Javapublic static DateTime now { get { return now == null ? DateTime.now() : now; } set; }
Datetime.now()
, otherwise, it will take the set value fornow
. - Ensure that your code references
Utility.now
instead ofSystem.now()
so that the time manipulation is effective during the test execution. - Set the
Utility.now
variable in your test method.Java@isTest public static void getLeadsTest() { Date myDate = Date.newInstance(2025, 11, 18); Time myTime = Time.newInstance(3, 3, 3, 0); DateTime dt = DateTime.newInstance(myDate, myTime); Utility.now = dt; Test.startTest(); //run your code for testing Test.stopTest(); }
Conclusion
Effective testing is crucial to ensuring that your Salesforce applications perform as expected under various conditions. By using the strategies outlined in this article — such as loading test data, leveraging Test.startTest()
and Test.stopTest()
, manipulating timestamps, and utilizing Test.isRunningTest()
— you can write robust and efficient Apex test classes that cover a wide range of use cases.
Opinions expressed by DZone contributors are their own.
Comments