Skip to main content

SF-9203 · Concept · Medium

What is Test.startTest() and Test.stopTest()?

✓ Verified by Vikas Singhal · Last reviewed 5/17/2026 · Updated for Spring '26

Test.startTest() and Test.stopTest() are a pair of marker methods you wrap around the code under test. They do two distinct, important things.

What they actually do

@isTest
static void example() {
    // 1. Test setup — uses test method's governor limits
    List<Account> accounts = new List<Account>();
    for (Integer i = 0; i < 200; i++) accounts.add(new Account(Name = 'Acc ' + i));
    insert accounts;

    Test.startTest(); // 2. RESET — fresh limits start here

    // 3. Code under test runs with full, fresh limits
    AccountService.processAll(accounts);

    Test.stopTest();  // 4. ASYNC FORCED — wait for queued jobs to finish

    // 5. Assertions — verify results
    System.assertEquals(200, [SELECT COUNT() FROM Task]);
}

Effect 1: governor limit reset

Setting up test data eats SOQL queries, DML statements, CPU time. If your test method inserts 200 records, that’s 1 DML and a chunk of CPU before the production code even runs.

Test.startTest() resets the governor counters. The code between startTest() and stopTest() gets the full fresh allocation — 100 SOQL, 150 DML, 10 seconds of CPU. This means the test is measuring your production code’s footprint, not your test setup’s footprint.

You can call Test.startTest()/Test.stopTest() only once per test method. There’s no nesting.

Effect 2: async work executes synchronously at stopTest()

This is the magic that makes async testing possible. Any of the following queued between startTest and stopTest will run to completion before stopTest() returns:

  • @future methods
  • Queueable jobs (System.enqueueJob)
  • Batch jobs (Database.executeBatch)
  • Scheduled jobs (System.schedule)

So a test for a Queueable can be:

@isTest
static void asyncJob_processesRecords() {
    Account a = new Account(Name = 'Test');
    insert a;

    Test.startTest();
    System.enqueueJob(new MyAsyncJob(new List<Account>{a}));
    Test.stopTest(); // <-- Queueable executes here, synchronously

    // Now assert against the result
    System.assertEquals('Processed', [SELECT Status__c FROM Account WHERE Id = :a.Id].Status__c);
}

Without Test.stopTest(), the Queueable would never run inside the test transaction — the assertion would fail.

What doesn’t get reset

Test.startTest() resets per-transaction governor limits. It doesn’t reset:

  • 24-hour API limits
  • Org-wide async job allocations (in tests these are mocked anyway)
  • The number of records you’ve already inserted (you’re still in the same transaction)

Common interview follow-ups

  • Do you have to use Test.startTest()? — Not strictly, but you almost always should. Without it, your test is measuring the wrong footprint and async code won’t execute.
  • Can you call Test.startTest() twice? — No. One pair per test method.
  • What happens if your code throws inside Test.startTest/Test.stopTest? — Same as anywhere else — the exception propagates. Catch it with try/catch and assert via e.getMessage() if you’re testing an expected failure.
  • Why does Batch Apex limit you to 5 concurrent batches in tests? — Same as production, but stopTest forces them to run inside the test, so they execute serially.

Verified against: Apex Developer Guide — Test Class. Last reviewed 2026-05-17 for Spring ‘26 release.