Writing the same test logic over and over with different inputs is tedious and error-prone.
@ParameterizedTest lets you run the same test method multiple times with
different arguments. It is one of JUnit 5's most powerful features.
Your First Parameterized Test
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class ParameterizedTestDemo {
@ParameterizedTest
@ValueSource(strings = {"racecar", "radar", "level"})
void shouldDetectPalindromes(String word) {
assertTrue(isPalindrome(word));
}
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void shouldAcceptPositiveNumbers(int number) {
assertTrue(number > 0);
}
}
Each value in @ValueSource is passed as an argument to the test method.
The test runs once for each value. So the palindrome test runs 3 times, the numbers
test runs 5 times.
Adding Display Names
@ParameterizedTest(name = "palindrome check: \"{0}\"")
@ValueSource(strings = {"racecar", "radar", "level"})
void palindromes(String word) {
assertTrue(isPalindrome(word));
}
@ParameterizedTest(name = "input: {0}")
@ValueSource(ints = {1, 2, 3, 4, 5})
void positiveNumbers(int number) {
assertTrue(number > 0);
}
Multiple Arguments
Some sources provide multiple arguments per invocation:
@ParameterizedTest
@CsvSource({
"1, 2, 3",
"5, 5, 10",
"0, 0, 0",
"-1, 1, 0"
})
void addition(int a, int b, int expected) {
assertEquals(expected, calc.add(a, b));
}
@ParameterizedTest
@CsvSource({
"hello, HELLO",
"world, WORLD",
"junit, JUNIT"
})
void upperCase(String input, String expected) {
assertEquals(expected, input.toUpperCase());
}
Available Sources
@ValueSource - Single values (strings, ints, etc.)
@CsvSource - Comma-separated values
@CsvFileSource - CSV file input
@MethodSource - Method that provides arguments
@EnumSource - Enum values
@ArgumentsSource - Custom argument provider
@MethodSource
For complex arguments, use a static method to provide them:
@ParameterizedTest
@MethodSource("stringProvider")
void withMethodSource(String argument) {
assertNotNull(argument);
}
static Stream<String> stringProvider() {
return Stream.of("apple", "banana", "cherry");
}
@ParameterizedTest
@MethodSource("additionCases")
void addition(int a, int b, int expected) {
assertEquals(expected, calc.add(a, b));
}
static Stream<Arguments> additionCases() {
return Stream.of(
Arguments.of(1, 2, 3),
Arguments.of(5, 5, 10),
Arguments.of(-1, 1, 0)
);
}
@EnumSource
@ParameterizedTest
@EnumSource(TimeUnit.class)
void allTimeUnits(TimeUnit unit) {
assertNotNull(unit.name());
assertTrue(unit.toMillis(1) > 0);
}
@ParameterizedTest
@EnumSource(value = TimeUnit.class, names = {"SECONDS", "MINUTES"})
void selectedTimeUnits(TimeUnit unit) {
assertEquals(TimeUnit.SECONDS, unit);
}
@CsvFileSource
// data.csv:
// input,expected
// "hello",5
// "world",5
// "junit",5
// "",0
@ParameterizedTest
@CsvFileSource(resources = "/data.csv", numLinesToSkip = 1)
void wordLength(String input, int expected) {
assertEquals(expected, input.length());
}
Best Practices
- Use meaningful parameter names with
@ParameterizedTest(name = ...) - Combine with
@DisplayNamefor even better readability - Keep each test case simple โ one assertion per parameterized test
- Use
@CsvFileSourcefor large test data sets - Avoid parameterized tests with more than 3-4 arguments per invocation