HttpCalloutMock, and you should use StubProvider to mock class dependencies. Finally, never rely on existing org data; ban SeeAllData=true unless absolutely necessary.
Module Map — 360° View
MODULE 8 root: 'Prove behavior, not coverage %' ├─ Test data patterns (8.1) — TestDataFactory, @testSetup, no SeeAllData [Current Topic] ├─ Mocking callouts & dependencies (8.2) — HttpCalloutMock, StubProvider └─ Coverage % vs test QUALITY (8.3) — asserts on behavior + negatives LINKS → Design patterns enable mocking (7) · CI gates run tests (12)
8.1 Test Data Factories
A test data factory centralizes how you create records for your test classes. By using a single utility class to stamp out records with sensible defaults, you build a safety net against org changes.
- Single Source of Truth: One class produces valid sObjects. It handles the mandatory fields and sensible defaults while letting you override specific values per test.
- DRY Principle (Don't Repeat Yourself): When a new required field or validation rule lands in production, you fix one class instead of hunting down 400 failing test methods.
- Performance Boosts: Combine the factory with
@testSetupto create shared data just once per test class. Salesforce rolls back the database state between methods, drastically cutting your suite's runtime. - Isolation:
SeeAllData=falseis the default and should be strictly enforced. Tests must construct their own universe.
The Pain of Inline Test Data
new Account(Name = 'Test')) without the new field. You now have to update 400 files. If you used TestDataFactory.createAccount(), the fix is exactly one line of code.
A new validation rule is deployed: Every Delivery__c record now requires a Service_Type__c. By the next morning, 400 test methods are glowing red.
- The Bad Way: Every test built its own records from scratch. You now have 400 copies of
new Delivery__c(...)missing the field. Fixing these is pure noise and wastes an entire day. Furthermore, each test hides private, undocumented assumptions about what constitutes a "valid" delivery. - The Good Way: You have one
TestDataFactorywith builder methods likemakeDeliveries(n, overrides). The factory provides defaults that keep records valid, while allowing overrides for specific test scenarios. Insertion is optional (pure logic tests don't need real DML), and graph helpers can build entire hierarchies (Account → Contacts → Deliveries) in one call.
๐งญ 360 Card — Test Data Factories
- Rule: All test data is built in one central utility class, providing sensible defaults and allowing specific overrides.
- Gain: A new required field or validation rule breaks exactly one recipe, not hundreds of tests.
- Price: The factory becomes a massive shared dependency. A sloppy change to a default value can unintentionally ripple through and break the entire suite.
- Limits:
SeeAllData=trueis almost never defensible. Use@testSetupto create records once per test class. Make DML insertion optional in your factory methods because pure-logic tests run faster on in-memory records. - The Trap: Writing test data inline is fast the first time. It is also the exact habit that creates the 400-plate problem later.
- At Volume: If a test suite takes 90 minutes to run, it's usually because data is built per method instead of per class, pure logic tests are hitting the database, or automation is firing wildly during the setup phase.
๐ฏ Core Q&A
Build your factory using a builder-style pattern:
- Provide methods like
make(n, overrides)that return valid records. - Make insertion optional. Many unit tests testing pure logic only need in-memory records.
- Create composition helpers that build out entire object graphs (e.g., an Account containing Contacts containing Cases).
The value goes far beyond DRY. It prevents test-data drift. When developers invent their own records inline, they embed hidden assumptions. A validation rule change will break a random scatter of tests with misleading errors. A factory ensures that tribal org knowledge (like mandatory record types or lookups) becomes executable, centralized code.
Code Implementation
// 1. One factory that builds test data for every test in the org.
@isTest
public class TestFactory {
// 2. Ask for how many records, and whether to insert them into the DB.
// One recipe, many tests.
public static List<Account> createAccounts(Integer n, Boolean doInsert) {
List<Account> accs = new List<Account>();
for (Integer i = 0; i < n; i++) {
// Apply sensible defaults required by the org
accs.add(new Account(Name = 'Acct ' + i, Industry = 'Technology'));
}
// 3. Insert only when the test needs real records in the database.
if (doInsert) {
insert accs;
}
return accs;
}
}
Follow-ups (Scenario-Based)
A: Almost never. The only survivors are objects that genuinely cannot be created in a test context. This used to include things like standard pricebooks, but Test.getStandardPricebookId() eliminated that excuse. Every test using SeeAllData=true is permanently coupled to its specific environment. It might pass in a sandbox, fail immediately after a data refresh, and physically cannot run in a scratch org. If a developer claims they need it, they are usually missing a factory recipe or dealing with an architecture smell that should be redesigned.
A: Measure first. Use the Tooling API to expose per-class runtimes; the distribution is always skewed. The top offenders usually share four fatal flaws:
- Data is created per-method instead of being centralized once in a
@testSetupblock. - Gratuitous volume. Inserting 251 records to test bulkification everywhere, when 2 records prove the logic just fine. Keep a few true 200-record bulk tests strictly on the trigger paths.
- DML-heavy tests for pure logic. Stubbing dependencies makes these instant.
- Cascading automation firing during the data setup phase. Use a bypass switch (e.g., custom metadata or static variables) to disable triggers during the data-arrangement phase, since that automation isn't what you are actively testing.