Unravel โบ Java concepts โบ 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
One recipe, many ingredient lists: the cook follows the same steps for each row of a table and reports separately on every dish.
- 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@MethodSource("cases") calls a static method returning Stream<Arguments> for complex objects; @EnumSource(Status.class) runs for each enum constant
- 3@Nested groups tests in inner classes ("when the cart is empty"); @BeforeEach/@AfterEach run around every test, @BeforeAll once
- 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
- Each test gets a new instance of the test class by default, so fields cannot leak state between tests
- Table-driven tests find edge cases cheaply: empty, null, boundary, huge, unicode
- JUnit 5 is three parts: the Platform (launching), Jupiter (the annotations and engine you write against) and Vintage (running JUnit 4 tests)
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); } }}- line 7 uses CsvSource, the core of JUnit 5: parameterized tests and lifecycle
- line 10 uses ValueSource, the core of JUnit 5: parameterized tests and lifecycle
- line 12 uses Nested, the core of JUnit 5: parameterized tests and lifecycle
- 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
@Disabledwithout a reason and forgetting it. - Using JUnit 4
@Testwith 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:
- Annotations:
@ValueSource@CsvSource@MethodSource@EnumSource@RepeatedTest@Nested@Tag@Disabled@Timeout@TempDir@TestFactory@RegisterExtension@TestInstance@Orderand 4 more
Part of a learning path
- Errors, logging and tests in depth ยท previous: Other logging APIs: JUL, Log4j, System.out ยท next: AssertJ fluent assertions
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.