In JUnit 5, parameterized tests let you run the same test multiple times with different input values.
You use:
@ParameterizedTest
instead of:
@Test
Then you provide test data using a source annotation such as @ValueSource, @CsvSource, @MethodSource, or @EnumSource.
Basic Example with @ValueSource
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;
class NumberTest {
@ParameterizedTest
@ValueSource(ints = {1, 2, 5, 10})
void shouldBePositive(int number) {
assertTrue(number > 0);
}
}
This runs the test 4 times:
number = 1
number = 2
number = 5
number = 10
Strings with @ValueSource
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertFalse;
class StringTest {
@ParameterizedTest
@ValueSource(strings = {"hello", "junit", "test"})
void shouldNotBeBlank(String value) {
assertFalse(value.isBlank());
}
}
@ValueSource supports simple values such as:
@ValueSource(strings = {"a", "b"})
@ValueSource(ints = {1, 2, 3})
@ValueSource(longs = {1L, 2L})
@ValueSource(doubles = {1.5, 2.5})
@ValueSource(booleans = {true, false})
Multiple Arguments with @CsvSource
Use @CsvSource when each test case needs more than one value.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@ParameterizedTest
@CsvSource({
"1, 2, 3",
"5, 7, 12",
"10, -2, 8"
})
void shouldAddNumbers(int a, int b, int expected) {
int result = a + b;
assertEquals(expected, result);
}
}
Each CSV row maps to the test method parameters:
a, b, expected
Use @NullSource, @EmptySource, and @NullAndEmptySource
These are useful for testing validation logic.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;
class UsernameValidatorTest {
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {" ", " "})
void shouldRejectBlankUsernames(String username) {
assertTrue(username == null || username.isBlank());
}
}
This test runs with:
null
""
" "
" "
You can also use them separately:
@NullSource
@EmptySource
@NullAndEmptySource
Use @MethodSource for Complex Test Data
Use @MethodSource when test data is more complex or easier to build in Java code.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
class DiscountCalculatorTest {
@ParameterizedTest
@MethodSource("discountCases")
void shouldCalculateDiscount(int price, double discountRate, int expectedPrice) {
int result = (int) (price * (1 - discountRate));
assertEquals(expectedPrice, result);
}
static Stream<Arguments> discountCases() {
return Stream.of(
Arguments.of(100, 0.10, 90),
Arguments.of(200, 0.25, 150),
Arguments.of(80, 0.50, 40)
);
}
}
The method source is usually static, unless the test class uses a per-class test instance lifecycle.
Use @EnumSource for Enum Values
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class RoleTest {
enum Role {
ADMIN,
USER,
GUEST
}
@ParameterizedTest
@EnumSource(Role.class)
void shouldHaveValidRole(Role role) {
assertNotNull(role);
}
}
You can also include or exclude specific enum values:
@EnumSource(value = Role.class, names = {"ADMIN", "USER"})
or:
@EnumSource(
value = Role.class,
names = {"GUEST"},
mode = EnumSource.Mode.EXCLUDE
)
Naming Each Parameterized Test Run
You can customize the displayed name for each invocation:
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@ParameterizedTest(name = "{0} + {1} should equal {2}")
@CsvSource({
"1, 2, 3",
"5, 7, 12",
"10, -2, 8"
})
void shouldAddNumbers(int a, int b, int expected) {
assertEquals(expected, a + b);
}
}
This can show test names like:
1 + 2 should equal 3
5 + 7 should equal 12
10 + -2 should equal 8
Common placeholders include:
{index} the invocation index
{0} the first argument
{1} the second argument
{arguments} all arguments
Required Dependency
For Maven:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
For Gradle:
testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'
test {
useJUnitPlatform()
}
Quick Rule of Thumb
Use:
@ValueSourcefor one simple argument@CsvSourcefor several simple arguments@MethodSourcefor complex objects or generated cases@EnumSourcefor enum values@NullSource,@EmptySource, or@NullAndEmptySourcefor null/empty validation cases
A typical parameterized test looks like this:
@ParameterizedTest
@CsvSource({
"2, 3, 5",
"10, 5, 15"
})
void shouldAddNumbers(int a, int b, int expected) {
assertEquals(expected, a + b);
}
