Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

In one sentence: put record construction in one class so that when someone makes a field required next year, one file changes instead of forty test classes. That is the entire argument, and it is enough. The design question is only whether the factory returns records or inserts them, and the answer is returns.

Return, do not insert

@IsTest
public class TestFactory {

    public static Account account() {
        return new Account(
            Name     = 'Test Account ' + uniqueSuffix(),
            Industry = 'Technology'
        );
    }

    public static List<Opportunity> opportunities(Id accountId, Integer count) {
        List<Opportunity> out = new List<Opportunity>();
        for (Integer i = 0; i < count; i++) {
            out.add(new Opportunity(
                Name      = 'Opp ' + i,
                AccountId = accountId,
                StageName = 'Prospecting',
                CloseDate = Date.today().addDays(30)
            ));
        }
        return out;
    }
}

A factory that inserts forces a DML statement per call, so a test needing three object types spends three of its 150. Returning lets the caller insert once, and it lets a test modify a field before saving, which is most of what tests need to do.

Let the caller override

The failure mode of a factory is a method per variation: accountWithHighRevenue, accountInRetail, accountWithNoOwner. Twenty methods later nobody can find the one they want. Return the default and let the test change what it cares about:

Account a = TestFactory.account();
a.AnnualRevenue = 5000000;
insert a;

This reads better too. The test says which field matters to it, in the test, where someone debugging will look.

Uniqueness

Unique fields collide when a test class creates several records and when tests run in parallel. Generate a suffix rather than counting:

private static Integer counter = 0;
private static String uniqueSuffix() {
    counter++;
    return String.valueOf(Datetime.now().getTime()) + '-' + counter;
}

For an email, 'test' + uniqueSuffix() + '@example.invalid'. The .invalid top-level domain is reserved and can never route anywhere, which is a better habit than example.com in test data that might one day reach a real send.

Users belong in a separate method

Inserting a User in the same transaction as an Account throws MIXED_DML_OPERATION. Keep user creation in its own method and call it inside System.runAs, and mark the method clearly so nobody folds it into the general setup.

public static User standardUser(String profileName) {
    Profile p = [SELECT Id FROM Profile WHERE Name = :profileName LIMIT 1];
    return new User(
        ProfileId = p.Id,
        LastName  = 'Test',
        Email     = 'test' + uniqueSuffix() + '@example.invalid',
        Username  = 'test' + uniqueSuffix() + '@example.invalid',
        Alias     = 'tst',
        TimeZoneSidKey = 'GMT',
        LocaleSidKey = 'en_GB',
        EmailEncodingKey = 'UTF-8',
        LanguageLocaleKey = 'en_US'
    );
}

Setting TimeZoneSidKey to GMT deliberately is worth doing: a test that passes only because the running user is in a particular zone is a test that will fail for someone else.

Surviving a schema change

The whole point is that a new required field means editing one method. To keep that true:

  • No literal new Account(...) in test classes. Once one exists, the guarantee is gone.
  • No queries for org data inside the factory, except where unavoidable such as Profile and RecordType. Those need a filter that will still match after someone renames things.
  • No assumptions about validation rules. A factory record must satisfy the current rules, which means the factory is the place that breaks when a rule is added — deliberately, once.

Do you need a builder

Fluent builders, TestFactory.account().withRevenue(5000000).build(), read nicely and cost a class per object. On a large codebase with many required fields and deep hierarchies they earn it. On a typical org, a static method returning a record plus direct field assignment is less code and no harder to read.

Frequently asked

Should the factory class be @IsTest? Yes, so it does not count against org coverage. It also means production code cannot call it, which is correct.

How do I handle record types? A method taking a developer name and looking it up through describe, not a hard-coded Id. Ids differ between orgs.

Can I load from a static resource? Yes, Test.loadData reads a CSV. It is good for large reference sets and bad for records you need to vary, because changing a value means editing a file you cannot see from the test.