How-to

How to generate an Apex test data factory

Every Salesforce project grows a test data factory, and almost every one of them was written the same way: insert a record, read the error, add a field, run it again. Here is the pattern worth writing by hand — and the command that reads the required fields out of the schema instead.

Short version

A factory is one static create<SObject>() method per object, each returning an un-inserted record with its required fields filled in. Write it by hand and the field list comes from repeated REQUIRED_FIELD_MISSING failures. Or run nimbus fixture Account Contact Contract and read it out of the schema in one pass.

The pattern

A test data factory exists for one reason: a test should say what it needs, not how to satisfy the org's required fields. Three rules carry most of the value.

  • Return, don't insert. The caller decides when the DML happens — and whether it happens inside Test.startTest().
  • One method per object. Not one method per test scenario. Scenario shaping belongs in the test, where a reader can see it.
  • Populate only what the schema demands. Anything else the factory sets is an invisible assumption every test inherits.
apex
@IsTest
public class TestDataFactory {

    static Account createAccount() {
        return new Account(
            Name = 'Test'
        );
    }

    static Contact createContact() {
        return new Contact(
            LastName = 'Test'
        );
    }
}

The test then reads as the thing it is testing, plus the one field that matters to it:

apex
Account a = TestDataFactory.createAccount();
a.Industry = 'Technology';   // the only field this test cares about
insert a;

Why the factory grows the way it does

Nobody designs the field list up front. It accretes from failures, in this order:

  1. REQUIRED_FIELD_MISSING on insert — a required field has no value. Add it, run again, get the next one. See the error reference for the full diagnosis, including the cases where the missing field is not the one named.
  2. A restricted picklist rejects your placeholder. 'Test' is not in the value set, so the value has to come from the field's own value set.
  3. A required lookup: the record needs a parent that has to exist first. Now the factory needs an insert order, not just a field list.

That last one is the step where hand-written factories usually go wrong — either by silently creating a parent nobody asked for, or by leaving the field unset and failing on insert in whichever test forgot.

Generate it from the schema instead

All three of those facts — required, createable, allowed picklist values, required lookups — are in the schema before you run anything. nimbus fixture reads them and writes the class:

bash
# Print a factory for three objects
nimbus fixture Account Contact Contract

# Save it into the project's default package
nimbus fixture Contract --write

# Name it something else
nimbus fixture Contract --class BillingFactory --write

By default the class goes to stdout — pipe it, diff it, read it before you commit to anything. --write saves the class and its -meta.xml into the default package; --api-version (default 62.0) stamps that metadata file and is only used with --write.

Standard objects work with no setup. Custom objects need one nimbus sync first — after that they generate exactly like standard objects, required fields and picklist value sets included.

What it does about required lookups

A required lookup pulls its parent object into the factory even if you never named it, and the methods come out parents-first so the chain can be inserted in that order. But the child method still leaves the lookup unset, with a TODO naming the field and the object it points at:

apex
static Contract createContract() {
    // TODO: required lookup AccountId (lookup to Account) — set after inserting its parent
    return new Contract(
        Status = 'In Approval Process'
    );
}

// Inserts the required parents, then returns an un-inserted Contract wired to them.
static Contract createContractWithParents() {
    Account parent0 = createAccount();
    insert parent0;
    Contract record = createContract();
    record.AccountId = parent0.Id;
    return record;
}

Which account a contract belongs to is a decision your test is making. The generated createContractWithParents() is the explicit opt-in for "I just need a valid record"; createContract() is what you call when you already have the parent you want:

apex
// I already have an account
Contract c = TestDataFactory.createContract();
c.AccountId = existingAccount.Id;
insert c;

// I just need a valid contract
Contract c2 = TestDataFactory.createContractWithParents();
insert c2;

Where the schema cannot tell it something — a picklist with no local value set, a required parent with no schema at all — it emits a named TODO and a warning rather than a fabricated value that compiles and then fails on insert.

Regenerating a factory that has drifted

Generation is also the cheapest answer to a factory somebody wrote eighteen months ago. The output is deterministic — the same project generates the same file every time — so you can regenerate against the current schema and diff:

bash
nimbus fixture Account Contact Contract > /tmp/factory-now.cls
diff force-app/main/default/classes/TestDataFactory.cls /tmp/factory-now.cls

The required field an admin added in the org three months ago shows up as a line in the diff instead of as a failing insert on your next deploy.

Where a generator stops

nimbus fixture gives you a class that compiles and inserts. It does not give you a test data strategy — static factory methods versus the builder pattern, when Test.loadData and a static resource beat both, and why a @testSetup that creates everything for everyone ages badly are all real tradeoffs. They are written up in Apex test data factories.

Next steps

Run the generator against the objects your slowest test class sets up, and compare its output with what that class does by hand. The gap is usually a field somebody guessed at, or a parent record that nothing needed.