BackendBench
Coffee

Unravel โ€บ Java concepts โ€บ Testing

Testing

JUnit 5: parameterized tests and lifecycle

A @ParameterizedTest runs one test method many times with different inputs from @ValueSource, @CsvSource or @MethodSource.

Learn these first

At a glance

Mental model

One recipe, many ingredient lists: the cook follows the same steps for each row of a table and reports separately on every dish.

What happens, in order
  1. 1@ParameterizedTest @ValueSource(strings = {"", " ", "\t"}) void blankIsInvalid(String s) runs three times@CsvSource({"1,2,3", "2,2,4"}) supplies several columns: void add(int a, int b, int sum).
  2. 2@MethodSource("cases") calls a static method returning Stream<Arguments> for complex objects; @EnumSource(Status.class) runs for each enum constant
  3. 3@Nested groups tests in inner classes ("when the cart is empty"); @BeforeEach/@AfterEach run around every test, @BeforeAll once
  4. 4@TempDir Path dir provides a fresh folder that is deleted afterwards; @Timeout(2) fails slow tests; @Disabled("reason") skips; @Tag("slow") selects groups in the build
Remember
Watch out
Writing a loop inside a test instead of a parameterized testSharing mutable static state between testsUsing @Disabled without a reason and forgetting itUsing JUnit 4 @Test with JUnit 5 annotations (wrong import)
Example (highlighted lines explain it)
import org.junit.jupiter.params.ParameterizedTest;import org.junit.jupiter.params.provider.*;import org.junit.jupiter.api.*;import static org.junit.jupiter.api.Assertions.*;class CalcTest {  @ParameterizedTest(name = "{0} + {1} = {2}")  @CsvSource({"1,2,3", "0,0,0", "-1,1,0"})  void adds(int a, int b, int sum) { assertEquals(sum, a + b); }  @ParameterizedTest  @ValueSource(strings = {"", " "})  void blankIsBlank(String s) { assertTrue(s.isBlank()); }  @Nested  class WhenEmpty {    @Test @Timeout(1) void fast() { assertTrue(true); }  }}
  1. line 7 uses CsvSource, the core of JUnit 5: parameterized tests and lifecycle
  2. line 10 uses ValueSource, the core of JUnit 5: parameterized tests and lifecycle
  3. line 12 uses Nested, the core of JUnit 5: parameterized tests and lifecycle
  4. line 14 uses Timeout, the core of JUnit 5: parameterized tests and lifecycle
Read the full explanation (about 1 min)

A @ParameterizedTest runs one test method many times with different inputs from @ValueSource, @CsvSource or @MethodSource. @Nested, @TempDir, @Timeout and @Tag organise and protect tests.

A mental model

One recipe, many ingredient lists: the cook follows the same steps for each row of a table and reports separately on every dish.

How it works

@ParameterizedTest @ValueSource(strings = {"", " ", "\t"}) void blankIsInvalid(String s) runs three times. @CsvSource({"1,2,3", "2,2,4"}) supplies several columns: void add(int a, int b, int sum).

@MethodSource("cases") calls a static method returning Stream<Arguments> for complex objects; @EnumSource(Status.class) runs for each enum constant.

@Nested groups tests in inner classes ("when the cart is empty"); @BeforeEach/@AfterEach run around every test, @BeforeAll once.

@TempDir Path dir provides a fresh folder that is deleted afterwards; @Timeout(2) fails slow tests; @Disabled("reason") skips; @Tag("slow") selects groups in the build.

Going deeper

Each test gets a new instance of the test class by default, so fields cannot leak state between tests. @TestInstance(PER_CLASS) changes that and allows non-static @BeforeAll.

Table-driven tests find edge cases cheaply: empty, null, boundary, huge, unicode. Give them readable names: @ParameterizedTest(name = "{0} is invalid").

Tests should not depend on order (@Order is for rare integration scenarios) or on the clock, network or random data without control.

Under the hood

JUnit 5 is three parts: the Platform (launching), Jupiter (the annotations and engine you write against) and Vintage (running JUnit 4 tests). A parameterized test is a template: the engine invokes it once per argument set, as a separate reportable test.

Common mistakes

  • Writing a loop inside a test instead of a parameterized test.
  • Sharing mutable static state between tests.
  • Using @Disabled without a reason and forgetting it.
  • Using JUnit 4 @Test with JUnit 5 annotations (wrong import).

How Unravel recognises it

When you paste code, Unravel looks for these signs of junit 5: parameterized tests and lifecycle. Spotting them yourself is a good habit when you read unfamiliar code:

Part of a learning path

Related concepts

Frequently asked questions

What is JUnit 5: parameterized tests and lifecycle?

A @ParameterizedTest runs one test method many times with different inputs from @ValueSource, @CsvSource or @MethodSource. @Nested, @TempDir, @Timeout and @Tag organise and protect tests.

What are the most common mistakes with JUnit 5: parameterized tests and lifecycle?

Writing a loop inside a test instead of a parameterized test. Sharing mutable static state between tests. Using @Disabled without a reason and forgetting it. Using JUnit 4 @Test with JUnit 5 annotations (wrong import).

How can I see JUnit 5: parameterized tests and lifecycle in real code?

Paste your own Java into Unravel and it will point out where junit 5: parameterized tests and lifecycle appears, explain how it works at the detail level you choose, and draw the flow of the code around it.