Let us take a deeper look at the different argument sources available for parameterized tests. Each source is designed for a specific use case, and knowing when to use each one will make your tests more readable and maintainable.
@ValueSource
The simplest source. Provides single values of the same type:
@ParameterizedTest
@ValueSource(strings = {"a", "ab", "abc", "abcd"})
void stringsOfIncreasingLength(String s) {
assertTrue(s.length() > 0);
}
@ParameterizedTest
@ValueSource(ints = {1, 3, 5, 7, 9})
void oddNumbers(int n) {
assertEquals(1, n % 2);
}
@ParameterizedTest
@ValueSource(doubles = {1.0, 2.5, 3.14})
void positiveDoubles(double d) {
assertTrue(d > 0);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void allBooleans(boolean b) {
assertNotNull(Boolean.valueOf(b));
}
Supported types: String, int, long,
double, float, short, byte,
char, boolean.
@CsvSource
Provides multiple arguments per invocation using comma-separated values:
@ParameterizedTest
@CsvSource({
"apple, 5",
"banana, 6",
"'hello world', 11",
"a, 1"
})
void stringLength(String word, int expectedLength) {
assertEquals(expectedLength, word.length());
}
// With explicit delimiters
@ParameterizedTest
@CsvSource(delimiter = '|', useHeadersInDisplayName = true, textBlock = """
INPUT | EXPECTED
hello | 5
world | 5
junit | 5
""")
void length(String input, int expected) {
assertEquals(expected, input.length());
}
@CsvFileSource
When you have lots of test data, put it in a CSV file:
// src/test/resources/test-data.csv
// input,expected
// hello,5
// world,5
// junit,5
// "",0
// "a",1
@ParameterizedTest
@CsvFileSource(
resources = "/test-data.csv",
numLinesToSkip = 1
)
void stringLength(String input, int expected) {
assertEquals(expected, input.length());
}
@MethodSource
The most flexible source. Provide arguments from a static method:
// Simple stream of values
@ParameterizedTest
@MethodSource("singleValues")
void testSingle(String value) {
assertNotNull(value);
}
static Stream<String> singleValues() {
return Stream.of("alpha", "beta", "gamma");
}
// Stream of Arguments for multiple params
@ParameterizedTest
@MethodSource("calculationData")
void testCalculation(int a, int b, int expected) {
assertEquals(expected, calc.add(a, b));
}
static Stream<Arguments> calculationData() {
return Stream.of(
Arguments.of(1, 1, 2),
Arguments.of(0, 0, 0),
Arguments.of(-1, 1, 0),
Arguments.of(Integer.MAX_VALUE, 0, Integer.MAX_VALUE)
);
}
// Factory method pattern
@ParameterizedTest
@MethodSource("com.example.TestDataFactory#userScenarios")
void testUserScenarios(User user, boolean expected) {
assertEquals(expected, service.isValid(user));
}
@EnumSource
// Test all enum values
@ParameterizedTest
@EnumSource(TimeUnit.class)
void allTimeUnits(TimeUnit unit) {
assertNotNull(unit);
assertTrue(unit.toMillis(1) > 0);
}
// Select specific values
@ParameterizedTest
@EnumSource(value = TimeUnit.class,
names = {"SECONDS", "MINUTES", "HOURS"})
void selectedTimeUnits(TimeUnit unit) {
assertTrue(unit.toMillis(1) > 0);
}
// Exclude specific values
@ParameterizedTest
@EnumSource(value = TimeUnit.class,
mode = EnumSource.Mode.EXCLUDE,
names = {"NANOSECONDS", "MICROSECONDS"})
void nonMicroUnits(TimeUnit unit) {
assertTrue(unit.toMillis(1) >= 1);
}
@ArgumentsSource - Custom Provider
class CustomArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(
ExtensionContext context) {
return Stream.of(
Arguments.of("input1", "expected1"),
Arguments.of("input2", "expected2"),
Arguments.of("input3", "expected3")
);
}
}
@ParameterizedTest
@ArgumentsSource(CustomArgumentsProvider.class)
void customSource(String input, String expected) {
assertEquals(expected, transform(input));
}
Decision Guide
Need single values? โ @ValueSource
Multiple values, same line? โ @CsvSource
Lots of data? โ @CsvFileSource
Complex objects? โ @MethodSource
Enum values? โ @EnumSource
Full control? โ @ArgumentsSource