Using JUnit 5 @ParameterizedTest with @ValueSource for Data‑Driven Tests
Learn how to replace repetitive @Test methods with a single parameterized test that runs the same assertions against multiple input values, improving maintainability and clarity.
07 Dec 2025, 19:37 UTC

Desired outcome
Replace a set of nearly identical @Test methods with a single parameterized test that runs the same assertion logic against multiple input values. This reduces duplication, makes it easier to add new cases, and gives the test runner a clear per‑iteration report.
Prerequisites
- Java 8 or later (the JUnit Jupiter engine requires at least Java 8).
- A build tool that can fetch Maven coordinates (Maven, Gradle, or similar).
- Add the JUnit Jupiter dependency. For Maven, include the following in
pom.xml(replace<version>with a current release, e.g., 5.10.0):
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
Ensure the test source directory (src/test/java) is on the test classpath.
Procedure
- Create a test class in the test source tree, e.g.,
src/test/java/com/example/StringUtilsTest.java. - Annotate the test method with
@ParameterizedTestand provide a value source with@ValueSource. - Declare a method parameter that matches the type of the values supplied by
@ValueSource. - Place the assertion logic inside the method, using the parameter as the input.
Example: testing a simple utility method that trims whitespace and converts to lower case.
package com.example;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class StringUtilsTest {
/** Utility method under test */
private String normalize(String input) {
return input == null ? null : input.trim().toLowerCase();
}
@ParameterizedTest
@ValueSource(strings = {" Hello World ", "JAVA", " test ", ""})
void normalize_returnsExpected(String raw) {
String expected = raw == null ? null : raw.trim().toLowerCase();
assertEquals(expected, normalize(raw), "Normalization failed for input: '" + raw + "'");
}
}
The method normalize_returnsExpected will be invoked once for each string in the @ValueSource array.
Expected checks
- When you run the test suite (e.g.,
./mvnw testor./gradlew test), the test runner will report one execution per value. - In the console output you should see something like:
├── StringUtilsTest ✔
│ ├─ normalize_returnsExpected[0] ✔
│ ├─ normalize_returnsExpected[1] ✔
│ ├─ normalize_returnsExpected[2] ✔
│ └─ normalize_returnsExpected[3] ✔
Each bracketed index corresponds to the iteration number; the failure (if any) will be attributed to that specific input.
target/surefire-reports) or JUnit Platform report and verify that each iteration appears with its display name (default includes the index and the value).@ParameterizedTest(name = "[{index}] {0}")
@ValueSource(strings = {"foo", "bar", "baz"})
void testWithName(String value) { /* … */ }
This makes the report show [0] foo, [1] bar, etc., confirming that the binding works.
Recovery options (when a test is flaky or needs extra validation)
- Repeated execution: If the test occasionally fails due to external state, wrap it with
@RepeatedTestinstead of or in addition to parameterization. - Exception handling: To assert that a particular input throws an exception, use
assertThrowsinside the method:
@ParameterizedTest
@ValueSource(strings = {"", " "})
void normalize_throwsOnBlank(String input) {
assertThrows(IllegalArgumentException.class, () -> {
normalize(input); // assuming the method throws for blank strings
});
}
- Logging: For debugging, place a
System.out.printlnor logger statement inside the method; each iteration will produce its own log line. - Alternative sources: When you need null, empty strings, or more complex objects, consider
@NullSource,@EmptySource,@EnumSource, or a custom@ArgumentsSource.
Limitations
- Do not combine
@ParameterizedTestwith@Teston the same method; JUnit will treat it purely as a parameterized test, and a missing parameter source results in aNoSuchMethodExceptionat runtime. - Avoid mutating the parameter value inside the test method. If the method alters the parameter (e.g., by calling a non‑pure method that changes the object), later iterations may see the altered state, leading to non‑deterministic failures.
- The
@ValueSourceannotation only supports primitive types,String,Class, and enums. For more complex objects, use@MethodSourceor@ArgumentsSource.
Practical verification
- Run the test suite from the command line:
# Maven
./mvnw test
# Gradle
./gradlew test
- Check the console output for a line per iteration (as shown in the Expected checks section).
- Open the test report (
target/surefire-reports/TEST-*.xmlfor Maven orbuild/reports/tests/test/index.htmlfor Gradle) and confirm that each iteration is listed with a distinct display name. - If you added a custom
nameattribute, verify that the parameter values appear in the report names.
If the report shows only a single test entry or the iterations are missing, double‑check that the junit-jupiter dependency is present and that the test class is not accidentally excluded by @Tag or build‑scope filters.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.