Skip to content

Lesson 02 · Parameterized & Structured Tests

Beyond the 1Z0-830 exam

Once you can write one test, the next skill is writing many without copy-paste, and organizing them so failures are easy to read. JUnit 5's parameterized, nested, dynamic, and tagged tests do exactly that.

Objectives

After this lesson you will be able to:

  • Run one test body across many inputs with @ParameterizedTest + a source.
  • Group related tests with @Nested and name suites with @DisplayName.
  • Generate tests at runtime with @TestFactory (dynamic tests).
  • Select subsets with @Tag.

Parameterized tests — one body, many inputs

Replace copy-pasted near-identical tests with a single body fed by a source:

java
@ParameterizedTest(name = "{0} cents @ rate 2.0 -> {1}")
@CsvSource({"0, 0", "50, 100", "1000, 2000"})
void converts(long input, long expected) {
    assertThat(svc.quote(input, "X").convertedCents()).isEqualTo(expected);
}

Each row runs as a separate, independently reported test case — a failure pinpoints the exact input.

Common sources

SourceFeedsUse for
@ValueSource(ints/strings/…)one argumenta single varying value
@CsvSource({"a,1", "b,2"})multiple args (inline)small input→expected tables
@CsvFileSource(resources=…)multiple args (file)larger data sets
@MethodSource("provider")any args, incl. objectscomputed/complex arguments
@EnumSource(Day.class)enum constantsevery value of an enum
@NullAndEmptySourcenull and emptyboundary inputs for strings/collections
java
static Stream<Arguments> cases() {
    return Stream.of(Arguments.of("EUR", 0.9), Arguments.of("JPY", 150.0));
}
@ParameterizedTest
@MethodSource("cases")
void usesRate(String ccy, double rate) { /* ... */ }

Gotcha

@Test and @ParameterizedTest are mutually exclusive — a parameterized method must not also carry @Test, and it must declare at least one source. A parameterized method with no source is a configuration error, not a passing test.

Structuring with @Nested

Group related cases into inner classes for readable, hierarchical reports:

java
@Nested
@DisplayName("isWeekend()")
class WeekendDetection {
    @Test void fridayIsWeekday()   { assertThat(svc.isWeekend()).isFalse(); }
    @Test void saturdayIsWeekend() { assertThat(svc.isWeekend()).isTrue(); }
}

Each @Nested class can have its own @BeforeEach, so you set up a shared context for just that group. The report reads like an outline: isWeekend() › Saturday is the weekend.

Dynamic tests — generated at runtime

When the cases aren't known until runtime, @TestFactory returns a collection/stream of DynamicTest:

java
@TestFactory
Stream<DynamicTest> palindromes() {
    return Stream.of("level", "noon", "java")
        .map(s -> dynamicTest(s, () ->
            assertEquals(isPalindrome(s), new StringBuilder(s).reverse().toString().equals(s))));
}

Difference from parameterized tests: parameterized cases are declared (annotations, fixed at compile time); dynamic tests are computed at runtime. Reach for dynamic tests only when a parameterized source can't express the cases.

Tags — selecting subsets

@Tag("slow") labels tests so the build can include/exclude them:

java
@Tag("integration")
class DbRoundTripTest { /* ... */ }
bash
mvn test -Dgroups=fast            # run only @Tag("fast")
mvn test -DexcludedGroups=slow    # skip slow ones

SDET note

Tags are how Part B's infra-heavy labs (Testcontainers, UI) stay out of the default fast run — a tag plus a Maven profile excludes them so mvn test is quick and green, while CI runs the full set (Modules 15 & 20).

Key Takeaways

  • @ParameterizedTest + a source runs one body over many inputs, each reported separately; pick the source (@ValueSource/@CsvSource/@MethodSource/@EnumSource/…) to fit the data.
  • A parameterized method must not also be @Test and must declare a source.
  • @Nested groups tests into a readable hierarchy, each with its own setup.
  • @TestFactory generates tests at runtime when a static source can't.
  • @Tag + profiles select fast vs slow/integration subsets.

Lesson Quiz

Lesson Quiz · Parameterized & Structured Tests0 / 5
  1. What's the main benefit of @ParameterizedTest?

    • ATests run in parallel automatically
    • BOne test body runs across many inputs, each reported as its own case
    • CIt mocks dependencies
    • DIt replaces @BeforeEach
  2. You need to feed (String, double) pairs computed in code. Which source?

    • A@ValueSource
    • B@MethodSource
    • C@NullSource
    • D@EnumSource
  3. Can a method have both @Test and @ParameterizedTest?

    • AYes, always
    • BNo — they're mutually exclusive; a parameterized test needs a source instead
    • COnly with @Tag
    • DOnly for void methods
  4. What does @Nested provide?

    • AParallel execution
    • BA grouped, hierarchical structure with its own @BeforeEach for related tests
    • CParameter injection
    • DException assertions
  5. When are dynamic tests (@TestFactory) the right tool over parameterized tests?

    • AAlways
    • BWhen the cases must be computed at runtime and can't be expressed by a static source
    • CWhen you need mocks
    • DFor exception testing only

Next: Test Doubles & Mockito.