Control governor limits and test asynchronous code with Test.startTest() and Test.stopTest() in Apex. Learn patterns to write reliable, fast Salesforce tests.
## Why Test.startTest() and Test.stopTest() matter
Test.startTest() and Test.stopTest() are essential tools for reliable Apex unit tests. They give you a fresh set of governor limits and let the platform execute asynchronous jobs (Queueable, @future, Batchable) synchronously within the test context. Misusing them leads to flaky tests or missed assertions; using them correctly makes tests deterministic and focused.
## Key behaviors to remember
- Limits reset: Calling Test.startTest() resets per-transaction governor limits, so code executed between startTest and stopTest has its own limit budget.
- Async execution: Asynchronous jobs enqueued after Test.startTest() run when Test.stopTest() is called, enabling assertions on their effects.
- Not a magic isolation: startTest/stopTest do not isolate DML or queries from your setup; initial test data and DML still count toward the overall setup.
### Pattern: Setup before startTest
Best practice is to perform all test data creation and any expensive setup before you call Test.startTest(). This ensures the action under test gets the reset limits and that assertions are focused on behavior, not setup cost.
Example flow:
- Create records and insert
- Create Test.setMock or stubs if needed
- Test.startTest()
- Execute the method or trigger under test
- Test.stopTest()
- Assert results
This flow avoids consuming the restarted governor limits during setup and keeps the measured operation concise.
### Pattern: Testing asynchronous Apex
To test Queueable, @future, or Batchable logic:
- Enqueue the async job after Test.startTest()
- Call Test.stopTest() to force the platform to execute the job synchronously
- Query or assert side effects
Simple example:
- Test.startTest()
- System.enqueueJob(new MyQueueable())
- Test.stopTest()
- // Now assert expected changes
Without startTest/stopTest, the async job won’t run and assertions will fail.
### Tips and pitfalls
- Avoid large setup inside startTest(); that defeats its purpose.
- Use Test.getLimits() or Limits.getDMLStatements() if you need to assert limit usage, but prefer behavior-focused assertions.
- Test.isRunningTest() can alter runtime behavior — use sparingly and document it. Prefer injection/strategy patterns to avoid test-only code paths.
- For callout tests use Test.setMock before startTest() so the mock is active when the code under test executes.
## Quick checklist for each test
- Build test data and mocks first
- Call Test.startTest()
- Execute the code under test (enqueue async work if applicable)
- Call Test.stopTest()
- Assert state, results, and side effects
Conclusion
Mastering Test.startTest() and Test.stopTest() makes Apex tests more reliable and predictable, especially when dealing with asynchronous code and governor limits. Apply the setup-then-exercise pattern, keep assertions focused, and avoid test-only branching. Want to accelerate writing tests that follow these patterns? Try Test Class Generator to scaffold test classes that use startTest/stopTest correctly and save development time.