Test.startTest() and Test.stopTest() in Salesforce – A Complete Guide
Why Is This Topic Important?
Understanding Test.startTest() and Test.stopTest() is essential for every Salesforce Platform Developer I candidate. These two methods are foundational to writing effective, governor-limit-aware unit tests. They appear frequently on the certification exam, and mastering them demonstrates your ability to write robust Apex tests that properly isolate the code under test from the test setup logic. Without a solid grasp of these methods, you risk losing easy points on exam questions related to testing, debugging, and deployment.
What Are Test.startTest() and Test.stopTest()?
Test.startTest() is a method that marks the point in your test method where the actual testing begins. Everything before this call is considered test setup (creating records, preparing data, etc.). Once Test.startTest() is called, Salesforce resets the governor limits, giving your code under test a fresh set of governor limits.
Test.stopTest() marks the end of the test execution context. When this method is called, Salesforce enforces the completion of all asynchronous operations (such as @future methods, Queueable jobs, Batch Apex, and Scheduled Apex) that were initiated between Test.startTest() and Test.stopTest(). This makes asynchronous code testable in a synchronous manner.
How Do They Work?
Here is the general pattern:
1. Setup Phase – Insert test data, create records, and perform any prerequisite configuration. This code runs under its own set of governor limits.
2. Test.startTest() – Call this method. At this point, Salesforce resets all governor limit counters (SOQL queries, DML statements, CPU time, etc.) to zero. Your code under test now gets a completely fresh set of limits.
3. Execution Phase – Execute the code you want to test. This could be a trigger, a method call, a web service callout (with a mock), or initiating an asynchronous process.
4. Test.stopTest() – Call this method. Salesforce forces all asynchronous processes that were enqueued between startTest and stopTest to complete immediately and synchronously. Governor limits are also evaluated at this point for the code that ran between the two calls.
5. Assertion Phase – After stopTest(), query the database and use System.assert(), System.assertEquals(), or System.assertNotEquals() to verify the expected outcomes.
Example Structure:
@isTest
static void testAccountTrigger() {
// Setup phase – this does NOT count against the code-under-test limits
Account acc = new Account(Name = 'Test Account');
insert acc;
// Reset governor limits
Test.startTest();
// Code under test
acc.Name = 'Updated Account';
update acc;
// Force async completion and evaluate limits
Test.stopTest();
// Assertions
Account result = [SELECT Name FROM Account WHERE Id = :acc.Id];
System.assertEquals('Updated Account', result.Name);
}
Key Behaviors to Remember:
• Governor Limit Reset: The primary purpose of Test.startTest() is to provide a fresh set of governor limits so that data setup operations do not interfere with the limits available to the code being tested.
• Asynchronous Execution: Test.stopTest() forces all asynchronous operations (future methods, queueable, batch, scheduled) to execute synchronously. This means you can write assertions after Test.stopTest() and be confident that the async work has completed.
• One Pair Per Test Method: You can only call Test.startTest() and Test.stopTest() once per test method. Calling them more than once will result in a runtime error.
• Mixed DML Workaround: Test.startTest() and Test.stopTest() can help manage mixed DML errors in tests. Setup objects (like User) inserted before Test.startTest() exist in a different execution context than standard object DML inside the startTest/stopTest block, which can help avoid mixed DML exceptions in certain patterns (though using System.runAs() is the more common solution).
• Callout Mocks: When testing HTTP callouts, you set up the mock (Test.setMock()) before Test.startTest(), then invoke the callout logic between startTest and stopTest.
Common Misconceptions:
• Test.startTest() does not create a separate transaction. It simply resets the governor limit counters.
• Test.stopTest() does not roll back DML. All data changes persist within the test context (and are rolled back only at the very end of the entire test method, as all test data is automatically rolled back).
• Test.startTest() and Test.stopTest() are not required for a test to run or for code coverage. They are best practices for proper limit isolation and async testing, but test methods compile and run without them.
When to Use Them:
• When you need to ensure your code under test has its own governor limit allocation separate from your test setup.
• When testing asynchronous Apex (future, queueable, batch, scheduled) – Test.stopTest() is the only way to force these to run synchronously in a test context.
• When testing large-volume data operations where setup consumes significant governor limits.
• When testing callouts with mock responses.
Exam Tips: Answering Questions on Test.startTest() and Test.stopTest()
1. Look for governor limit questions: If an exam question asks how to ensure that test data setup does not consume the governor limits available to the code under test, the answer is Test.startTest().
2. Async testing questions: If a question asks how to test a @future method, a Queueable job, or a Batch Apex class, the correct approach involves calling the async code between Test.startTest() and Test.stopTest(). The assertions should come after Test.stopTest().
3. Remember the one-pair rule: If an answer option suggests calling Test.startTest() multiple times in the same test method, that option is incorrect.
4. Placement matters: On the exam, pay close attention to where Test.startTest() and Test.stopTest() are placed in code snippets. Setup should be before startTest(), the code under test between startTest() and stopTest(), and assertions after stopTest().
5. Assertions after stopTest(): If a question shows assertions placed between startTest() and stopTest() for async code, that pattern is problematic because the async code may not have completed yet. Correct placement is after stopTest().
6. Do not confuse with Test.isRunningTest(): Test.isRunningTest() is a different method entirely – it returns a Boolean indicating whether the code is being executed in a test context. Do not mix it up with startTest/stopTest.
7. Batch Apex specifics: When testing batch Apex, you call Database.executeBatch() between Test.startTest() and Test.stopTest(). The stopTest() call forces the batch to execute (start, execute, and finish methods all run). Query results and assertions should be placed after stopTest().
8. Elimination strategy: On tricky questions, eliminate answers that suggest alternatives like Thread.sleep(), manual delays, or multiple startTest() calls. These are common distractors.
9. Code coverage misconception: Some questions may try to imply that Test.startTest() is required for code coverage. It is not required for coverage – it is a best practice for limit management and async testing. Code between the two calls and outside of them both counts toward coverage.
10. Combine with System.runAs(): Some questions may combine Test.startTest()/stopTest() with System.runAs() to test code running as a specific user. These are complementary patterns and can absolutely be used together in the same test method.
By understanding these concepts thoroughly, you will be well-prepared to handle any exam question related to Test.startTest() and Test.stopTest() with confidence.