Skip to content

Lesson 02 · Data-Driven Tests & Fixtures

Beyond the 1Z0-830 exam

As a suite grows, test setup becomes the maintenance burden — change a constructor and a hundred tests break. Fixtures (builders, object mothers) and data-driven tests keep setup DRY, readable, and resilient.

Objectives

After this lesson you will be able to:

  • Build reusable fixtures with the test data builder and object mother idioms.
  • Write data-driven tests cleanly (recap of parameterized sources).
  • Use Faker for realistic data — and know its determinism caveat.
  • Apply setup/teardown patterns without leaking state.

The problem: brittle, noisy setup

java
User u = new User("Test User", "[email protected]", 30, List.of("USER"), true, null, Instant.now());

Every test that needs a user repeats this, and most of those fields are irrelevant to the test. Add a constructor parameter and every call site breaks. Fixtures fix both problems.

Test data builder — defaults + targeted overrides

A builder supplies valid defaults; a test overrides only the field under test:

java
User minor = aUser().withAge(17).build();   // age is the point; everything else is a sane default
java
public static final class UserBuilder {
    private String name = "Test User";
    private int age = 30;
    private List<String> roles = List.of("USER");
    public static UserBuilder aUser() { return new UserBuilder(); }
    public UserBuilder withAge(int age) { this.age = age; return this; }
    public UserBuilder withRoles(String... r) { this.roles = List.of(r); return this; }
    public User build() { return new User(name, age, List.copyOf(roles)); }
}

The test now reads as intent ("a user, aged 17"), and a new required field means one change — in the builder — not a hundred.

Object mother — named canonical instances

When you keep needing "a typical admin," give it a name:

java
User admin = Users.admin();      // canonical, ready-made
User minor = Users.minor();

Builder vs object mother: use the builder when a test needs a specific variation; use the object mother when it needs "a standard X" and the details don't matter. They compose — mothers are usually implemented with the builder.

SDET note

Keep fixtures in test scope (e.g. src/test), not production code — they're test support. (This course's labs keep them in main only so the lab tests can exercise them as the lesson's subject.)

Data-driven tests

Drive one assertion across a table of cases with parameterized sources (Module 14 L02), building each input fluently:

java
@ParameterizedTest(name = "age {0}, role {1} -> eligible {2}")
@CsvSource({"18, USER, true", "17, USER, false", "40, GUEST, false"})
void eligibility(int age, String role, boolean expected) {
    User u = aUser().withAge(age).withRoles(role).build();
    assertThat(isEligible(u)).isEqualTo(expected);
}

The table is the specification — adding a case is one line, and each row reports independently.

Faker — realistic data, with a caveat

Libraries like Datafaker generate plausible names, emails, addresses — handy for volume and for catching assumptions ("names have no apostrophes"). But random data fights determinism:

Trap — random fixtures and flakiness

Unseeded random data makes failures unreproducible: a test passes 99 runs then fails on the one input that trips a bug, and you can't reproduce it. Seed the faker (new Faker(new Random(42))) so the data is realistic and repeatable. Prefer fixed, meaningful fixtures for assertions; use faker for fields you don't assert on.

Setup/teardown without leaks

  • Build fresh state in @BeforeEach; the new-instance-per-test rule (Module 14) already isolates fields, but shared/static state (caches, files, DB rows) must be reset in @AfterEach.
  • A test that mutates shared state and doesn't clean up is the classic "passes alone, fails in the suite" bug.
  • Prefer immutable fixtures where possible — nothing to reset.

Key Takeaways

  • A test data builder gives valid defaults so each test overrides only the relevant field; one new field = one change.
  • An object mother exposes named canonical instances (Users.admin()); use builders for variations, mothers for "a standard X" — they compose.
  • Data-driven tests put the spec in a table; each row reports independently.
  • Faker adds realistic data but must be seeded for determinism; assert on fixed values.
  • Reset shared/static state in teardown; prefer immutable fixtures so there's nothing to reset.

Lesson Quiz

Lesson Quiz · Data-Driven Tests & Fixtures0 / 4
  1. What problem does a test data builder primarily solve?

    • ASlow tests
    • BBrittle, noisy setup — a test specifies only relevant fields and inherits valid defaults
    • CMocking the database
    • DParallel execution
  2. When is an object mother a better fit than a builder?

    • AWhen you need a specific one-off variation
    • BWhen you need 'a standard X' (e.g. a typical admin) and the details don't matter
    • CWhen mocking
    • DNever
  3. Why must Faker/Datafaker be seeded in tests?

    • ATo run faster
    • BSo generated data is repeatable — otherwise failures can't be reproduced
    • CTo avoid compile errors
    • DFaker requires a seed by law
  4. A test passes alone but fails in the full suite. Most likely cause?

    • AA compiler bug
    • BShared/static state mutated and not reset between tests
    • CToo many assertions
    • DUsing AssertJ

Next: BDD with Cucumber.