JUnit 5 @ParameterizedTest

The parameterized tests are used to run the same test method multiple times with different sets of input data. This is very useful when we need to test a functionality against multiple different input values. Rather than writing multiple test methods, each handling a different input case, parameterized tests consolidate these test cases into a single method.

JUnit 5 @ParameterizedTest annotation allows us to write parameterized tests in a clean and organized way. Let’s its purpose, how to use it, and its benefits.

1. Setup

Include junit-jupiter-params dependency in order to use parameterized tests. Find the latest version at this link.

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>5.8.1</version>
    <scope>test</scope>
</dependency>

2. @ParameterizedTest Example

Use @ParameterizedTest annotation to execute a test multiple times but with different arguments.
We do not need to use @Test annotation, instead, only @ParameterizedTest annotation is used on such tests.

@ParameterizedTest(...)
@ValueSource(...)
void testMethod(String param) {
  //...
}

We must declare at least one argument source that will provide the arguments for each invocation which will be consumed in the test method.

In the given example, testPalindrome will be invoked 2 times for each string in the @ValueSource annotation. We can access the argument using the method parameter word. Use name argument in the @ParameterizedTest annotation to customize the display message.

public class ParameterizedTests {
	
	@ParameterizedTest(name = "{index} - {0} is a palindrome")
	@ValueSource(strings = { "12321", "pop" })
	void testPalindrome(String word) {

	    assertTrue(word == null ? false : StringUtils.reverse(word).equals(word));
	}
}

3. Argument Providers

JUnit 5 uses ArgumentsProvider classes to supply the input parameters for your tests. You can implement a custom ArgumentsProvider or use the built-in providers provided by JUnit 5.

Let’s explore the built-in providers, first.

Argument ProviderDescription
@ValueSourceFor simple literal values such as primitives and strings. It does not support the null as a value.
@NullSourceFor a single null argument.
@EmptySourceFor a single empty argument of type String, List, Set, Map, or arrays.
@NullAndEmptySourceFor a null value and then an empty value.
@EnumSourceTest method is invoked for each enum constant at a time.
@MethodSourceFor one or more factory methods that generate a stream of arguments.
@CsvSourceFor argument lists as comma-separated values.
@CsvFileSourceFor reading the CSV tokens from a file.
@ArgumentsSourceFor a custom and reusable ArgumentsProvider.

The @ValueSource annotation does not support the null as a value. So by using @NullSource and @EmptySource with @ValueSource, we can test null, non-null and empty values, all in the same test.

Let’s understand each argument provider in more detail.

3.1. @ValueSource

Use @ValueSource for simple literal values such as primitives and strings.

  • It specifies a single array of values and can only be used for providing a single argument per parameterized test invocation.
  • Java supports autoboxing so we can consume the literals in their wrapper classes as well.
  • We can not pass null as an argument, even for String and Class types.
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testMethod(int argument) {
    //test code
}

@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testMethodWithAutoboxing(Integer argument) {
    //test code
}

3.2. @NullSource

It provides a single null argument to the annotated @ParameterizedTest method.

@ParameterizedTest
@NullSource
void testMethodNullSource(Integer argument) {
	assertTrue(argument == null);
}

3.3. @EmptySource

It provides a single empty argument to the annotated @ParameterizedTest method of the following types:

  • java.lang.String
  • java.util.List
  • java.util.Set
  • java.util.Map
  • primitive arrays (e.g. int[])
  • object arrays (e.g. String[])
@ParameterizedTest
@EmptySource
void testMethodEmptySource(String argument) {
	assertTrue(StringUtils.isEmpty(argument));
}

3.4. @NullAndEmptySource

It combines the functionality of @NullSource and @EmptySource. In the given example, the test method will be invoked two times – first with a null value and then with an empty value.

@ParameterizedTest
@NullAndEmptySource
void testMethodNullAndEmptySource(String argument) {
	assertTrue(StringUtils.isEmpty(argument));
}

3.5. @EnumSource

It provides a convenient way to use Enum constants. The test method will be invoked for each enum constant at a time.

In the given example, the test method will be invoked 4 times, once for each enum constant.

enum Direction {
	EAST, WEST, NORTH, SOUTH
}

@ParameterizedTest
@EnumSource(Direction.class)
void testWithEnumSource(Direction d) {
    assertNotNull(d);
}

3.6. @MethodSource

  • It is used to refer to one or more factory methods of the test class or external classes. The factory method must generate a stream of arguments, where each argument within the stream will be consumed by the annotated @ParameterizedTest method.
  • The factory method must be static, unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).
  • Also, the factory method must not take any method argument.
@ParameterizedTest
@MethodSource("argsProviderFactory")
void testWithMethodSource(String argument) {
    assertNotNull(argument);
}

static Stream<String> argsProviderFactory() {
    return Stream.of("alex", "brian");
}

If we do not explicitly provide a factory method name via @MethodSource, JUnit will search for a factory method that has the same name as the current @ParameterizedTest method by default.

So in the given example, if we do not provide the method name argsProviderFactory in @MethodSource annotation, Junit will search for a method name testWithMethodSource with return type Stream<String>.

@ParameterizedTest
@MethodSource
void testWithMethodSource(String argument) {
    assertNotNull(argument);
}

static Stream<String> testWithMethodSource() {
    return Stream.of("alex", "brian");
}

Streams for primitive types (DoubleStream, IntStream, and LongStream) are also supported.

@ParameterizedTest
@MethodSource("argsProviderFactory")
void testWithMethodSource(int argument) {
    assertNotEquals(9, argument);
}

static IntStream argsProviderFactory() {
    return IntStream.range(0, 10);
}

3.7. @CsvSource

It allows us to express argument lists as comma-separated values. Each CSV token represents a CSV line and results in one invocation of the parameterized test.

Set property ignoreLeadingAndTrailingWhitespace either true or false, which hints the Junit to honor or dishonor the whitespaces in CSV tokens.

@ParameterizedTest
@CsvSource(value = {
    "alex, 30",
    "brian, 35",
    "charles, 40"
}, ignoreLeadingAndTrailingWhitespace = true)
void testWithCsvSource(String name, int age) {
    assertNotNull(name);
    assertTrue(age > 0);
}

3.8. @CsvFileSource

It is very mi=uch similar to @CsvSource except we are reading the CSV tokens from a file instead of reading inline tokens. The CSV file may be read from the classpath or the local file system.

The default delimiter is a comma (,), but we can use another character by setting the delimiter attribute.

Note that any line beginning with a # symbol will be interpreted as a comment and will be ignored.

@ParameterizedTest
@CsvFileSource(resources = "employeeData.csv", numLinesToSkip = 0)
void testWithCsvFileSource(String name, int age) {
	assertNotNull(name);
    assertTrue(age > 0);
}

3.9. @ArgumentsSource

@ArgumentsSource can be used to specify a custom, reusable ArgumentsProvider.

@ParameterizedTest(name = "{index} - {0} is older than 40")
@ArgumentsSource(EmployeesArgumentsProvider.class)
void isEmployeeAgeGreaterThan40(Employee e) {
    assertTrue(Period.between(e.getDob(), LocalDate.now()).get(ChronoUnit.YEARS) > 40);
}

class EmployeesArgumentsProvider implements ArgumentsProvider {
    @Override
    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
        return Stream.of(
          Arguments.of(new Employee(1, "Alex", LocalDate.of(1980, 2, 3))),
          Arguments.of(new Employee(2, "Brian", LocalDate.of(1979, 2, 3))),
          Arguments.of(new Employee(3, "Charles", LocalDate.of(1978, 2, 3)))
        );
    }
}

4. Parameterized Tests with Multiple Arguments

To write tests that can consume multiple arguments, we can use the following annotations:

4.1. @CsvSource

As shown in the previous section 3.7, we can provide many literals and simple types of arguments using the @CsvSource annotation.

We need to provide all arguments in a single CSV token and then define the matching method arguments.

@ParameterizedTest
@CsvSource({
    "alex, 30, HR, Active",
    "brian, 35, Technology, Active",
    "charles, 40, Finance, Purged"
})
void testWithCsvSource(String name, int age, String department, String status) {
	//test code
}

4.2. Custom ArgumentsProvider

To provide multiple test arguments of complex or custom types, we should be using @ArgumentsSource annotation with ArgumentsProvider annotation.

In the given example, we are passing three arguments to the test method testArgumentsSource, of types Employee, LocalDate and enum constant of type Direction.

@ParameterizedTest
@ArgumentsSource(EmployeesArgumentsProvider.class)
void testArgumentsSource(Employee e, LocalDate date, Direction d) {
     
    // Use arguments to test functionality
    //...
    assertTrue(Period.between(e.getDob(), LocalDate.now()).get(ChronoUnit.YEARS) > 40);
    assertNotNull(date);
    assertNotNull(d);
}

class EmployeesArgumentsProvider implements ArgumentsProvider {
    @Override
    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
        return Stream.of(
          Arguments.of(new Employee(1, "Alex", LocalDate.of(1980, 2, 3)), LocalDate.now(), Direction.EAST),
          Arguments.of(new Employee(2, "Brian", LocalDate.of(1979, 2, 3)), LocalDate.now(), Direction.NORTH),
          Arguments.of(new Employee(3, "Charles", LocalDate.of(1978, 2, 3)), LocalDate.now(), Direction.SOUTH)
        );
    }

5. Conclusion

Junit 5 @ParameterizedTest annotation is very helpful in writing tests that must be invoked multiple times but with different arguments to test. Junit provides multiple ways that can be used declaratively to provide arguments to the test method.

We can combine different annotations to test various kinds of inputs in a single test as well.

There are some more complex concepts such as arguments aggregator, arguments converter etc. Please read the official Junit documentation for the latest information.

Happy Learning !!

Download Source Code

Comments

Subscribe
Notify of
guest
3 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode