Ch. 22 · Software Testing

Test Data Builders and Object Mothers

Build valid test objects with defaults and override only what a test cares about, to keep tests readable and robust.

~2 min readintermediateupdated Oct 5, 2026

A test that spells out every field of an object is noisy and breaks whenever the object gains a field. A builder supplies valid defaults and lets each test override only the fields it exercises, so the test states its intent and survives unrelated changes.

Before you start

You should be comfortable with test structure and objects. This article covers builders and their benefits.

Step-by-step walkthrough

Step 1: Provide a valid default

A builder function returns an object with sensible values for every required field, so the test starts from something valid. The test then fails only for the reason it is testing, not because a required field was missing.

Step 2: Override per test

Pass an overrides object merged over the defaults, so each test sets only the fields relevant to it. A test about inactive users sets status: 'inactive' and leaves the rest, which makes it obvious what the scenario is.

Step 3: Return fresh objects

Build a new object each call rather than sharing a module-level fixture, so one test cannot mutate the data another test uses. This removes an entire class of order-dependent failures.

Worked scenario

The builder defaults and the test overrides one field.

function makeUser(overrides = {}) {
  return { id: 'u-1', name: 'Ada', status: 'active', ...overrides };
}
test('inactive user cannot log in', () => {
  const user = makeUser({ status: 'inactive' });
  expect(canLogin(user)).toBe(false);
});
JavaScript

Walk through the example

makeUser returns a complete, valid user, so the test does not need to specify id or name. The override sets only status, which is the scenario under test. Each call produces a fresh object, so tests are independent.

Common mistake

Sharing a mutable fixture object across tests, so one test’s change leaks into another, or repeating every field in every test, which makes tests long and brittle to schema changes.

Verify the behavior

Add a required field to the domain object and confirm tests using the builder still pass with a default added in one place. Run tests in random order and confirm no failures from shared state. Confirm each test sets up only the fields it asserts on.

Interview exercise

Why is a builder better than a shared fixture object?

Answer and reasoning

A builder produces a fresh valid object per test and lets each test override only relevant fields, so tests are independent and readable. A shared fixture is a single mutable object whose state can leak between tests, causing order-dependent failures, and it hides what each test actually depends on.

Continue learning

Compare fixture isolation in Fixture isolation and test structure in Arrange, act, assert. Read the Martin Fowler on object mothers and try the Testing interview questions.

More in Software Testing

read ✓Software Testing · hard

Testing Time with an Injected Clock

Inject a clock instead of calling the system time, freeze it in tests, and remove the sleeps that make tests flaky.

~2 min readread →
read ✓Software Testing · mid

Code Coverage and Its Limits

Read line and branch coverage as a signal, not a goal, and avoid the tests that chase the number without checking behavior.

~2 min readread →
read ✓Software Testing · hard

Scoping End-to-End Tests

Reserve end-to-end tests for critical user journeys, use stable selectors, and avoid duplicating coverage the lower tests already have.

~2 min readread →
esc