@testSetup annotates a method that runs once at the start of a test class to create test data shared by every test method in that class. The platform then provides each test method with a copy of that data, automatically rolling back any changes between methods.
The shape
@isTest
private class OpportunityServiceTest {
@testSetup
static void makeData() {
Account acc = new Account(Name = 'Acme');
insert acc;
List<Opportunity> opps = new List<Opportunity>();
for (Integer i = 0; i < 10; i++) {
opps.add(new Opportunity(
Name = 'Opp ' + i,
AccountId = acc.Id,
StageName = 'Prospecting',
CloseDate = Date.today().addDays(30)
));
}
insert opps;
}
@isTest
static void closeAll_setsAllToClosedWon() {
Test.startTest();
OpportunityService.closeAllForAccount('Acme');
Test.stopTest();
Integer closedCount = [SELECT COUNT() FROM Opportunity WHERE StageName = 'Closed Won'];
System.assertEquals(10, closedCount);
}
@isTest
static void closeAll_skipsClosedLostOpps() {
// This test starts fresh — the testSetup data is restored
Opportunity oneLost = [SELECT Id FROM Opportunity LIMIT 1];
oneLost.StageName = 'Closed Lost';
update oneLost;
Test.startTest();
OpportunityService.closeAllForAccount('Acme');
Test.stopTest();
Integer closedWon = [SELECT COUNT() FROM Opportunity WHERE StageName = 'Closed Won'];
System.assertEquals(9, closedWon); // The lost one stays lost
}
}
What the platform does behind the scenes
- Calls
makeData()once. Records get real IDs. - Takes a snapshot of those records and their state.
- Before each
@isTestmethod, restores the snapshot so the test sees a clean copy of the data. - After each test, rolls back anything that test inserted/updated.
The restoration is fast — much faster than re-running the setup method for every test.
Why use it
- Speed. Setting up 10 Opportunities, an Account, and a Pricebook in every test method adds seconds per test. A class with 30 tests becomes a class with 3 minutes of test setup.
@testSetupdoes it once. - DRY. Test data setup is repetitive. Putting it in one place keeps tests focused on what they assert, not what they need.
- Predictability. Every test starts from a known, identical state.
Rules
- One
@testSetupmethod per class. Multiple methods will fail to compile. - Method must be
static voidand take no arguments. - Class-level data isolation rules apply. If the class has
@isTest(SeeAllData=true), the testSetup also sees org data. Otherwise, isolated. - Setup data is per class, not per project. Each test class has its own
@testSetup.
When not to use it
- Tests that need radically different data. If three of your test methods need entirely different fixtures, you’re not saving anything — write small
TestDataFactorycalls inside each. - Inside packages with strict managed dependencies. Some platform records (FeedItem, certain Setup Audit Trail rows) can’t be inserted reliably in
@testSetup.
@testSetup and IDs
The same record gets the same ID across all test methods in the class. So you can do:
@testSetup
static void setup() {
insert new Account(Name = 'Acme');
}
@isTest
static void findsByName() {
Account a = [SELECT Id FROM Account WHERE Name = 'Acme'];
// a.Id is stable across all test methods
}
Common interview follow-ups
- Can
@testSetupcall@testSetupfrom another class? — No, each class has its own. But it can call apublic staticmethod on a sharedTestDataFactory. - What governor limits does
@testSetupconsume? — Its own transaction’s limits. Test methods get fresh limits after. - What happens if
@testSetupthrows? — All test methods in the class fail with the setup error. Fix the setup; the tests can’t run.
Verified against: Apex Developer Guide — Using the @testSetup Annotation. Last reviewed 2026-05-17 for Spring ‘26 release.