Appearance
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 defaultjava
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
What problem does a test data builder primarily solve?
When is an object mother a better fit than a builder?
Why must Faker/Datafaker be seeded in tests?
A test passes alone but fails in the full suite. Most likely cause?
Next: BDD with Cucumber.