This version is still in development and is not considered stable yet.
For the latest stable version, please use JUnit 6.0.1!

Parameterized Classes and Tests

Parameterized tests make it possible to run a test method multiple times with different arguments. They are declared just like regular @Test methods but use the @ParameterizedTest annotation instead.

Parameterized classes make it possible to run all tests in a test class, including Nested Tests, multiple times with different arguments. They are declared just like regular test classes and may contain any supported test method type (including @ParameterizedTest) but annotated with the @ParameterizedClass annotation.

Parameterized classes are currently an experimental feature. You’re invited to give it a try and provide feedback to the JUnit team so they can improve and eventually promote this feature.

Regardless of whether you are parameterizing a test method or a test class, you must declare at least one source that will provide the arguments for each invocation and then consume the arguments in the parameterized method or class, respectively.

The following example demonstrates a parameterized test that uses the @ValueSource annotation to specify a String array as the source of arguments.

@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
	assertTrue(StringUtils.isPalindrome(candidate));
}

When executing the above parameterized test method, each invocation will be reported separately. For instance, the ConsoleLauncher will print output similar to the following.

palindromes(String) ✔
├─ [1] candidate = "racecar" ✔
├─ [2] candidate = "radar" ✔
└─ [3] candidate = "able was I ere I saw elba" ✔

The same @ValueSource annotation can be used to specify the source of arguments for a @ParameterizedClass.

@ParameterizedClass
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
class PalindromeTests {

	@Parameter
	String candidate;

	@Test
	void palindrome() {
		assertTrue(StringUtils.isPalindrome(candidate));
	}

	@Test
	void reversePalindrome() {
		String reverseCandidate = new StringBuilder(candidate).reverse().toString();
		assertTrue(StringUtils.isPalindrome(reverseCandidate));
	}
}

When executing the above parameterized test class, each invocation will be reported separately. For instance, the ConsoleLauncher will print output similar to the following.

PalindromeTests ✔
├─ [1] candidate = "racecar" ✔
│  ├─ palindrome() ✔
│  └─ reversePalindrome() ✔
├─ [2] candidate = "radar" ✔
│  ├─ palindrome() ✔
│  └─ reversePalindrome() ✔
└─ [3] candidate = "able was I ere I saw elba" ✔
   ├─ palindrome() ✔
   └─ reversePalindrome() ✔

Required Setup

In order to use parameterized classes or tests you need to add a dependency on the junit-jupiter-params artifact. Please refer to Dependency Metadata for details.

Consuming Arguments

Parameterized Tests

Parameterized test methods consume arguments directly from the configured source (see Sources of Arguments) following a one-to-one correlation between argument source index and method parameter index (see examples in @CsvSource). However, a parameterized test method may also choose to aggregate arguments from the source into a single object passed to the method (see Argument Aggregation). Additional arguments may also be provided by a ParameterResolver (e.g., to obtain an instance of TestInfo, TestReporter, etc.). Specifically, a parameterized test method must declare formal parameters according to the following rules.

  • Zero or more indexed parameters must be declared first.

  • Zero or more aggregators must be declared next.

  • Zero or more arguments supplied by a ParameterResolver must be declared last.

In this context, an indexed parameter is an argument for a given index in the Arguments provided by an ArgumentsProvider that is passed as an argument to the parameterized method at the same index in the method’s formal parameter list. An aggregator is any parameter of type ArgumentsAccessor or any parameter annotated with @AggregateWith.

Parameterized Classes

Parameterized classes consume arguments directly from the configured source (see Sources of Arguments); either via their unique constructor or via field injection. If a @Parameter-annotated field is declared in the parameterized class or one of its superclasses, field injection will be used. Otherwise, constructor injection will be used.

Constructor Injection

Constructor injection can only be used with the (default) PER_METHOD test instance lifecycle mode. Please use field injection with the PER_CLASS mode instead.

For constructor injection, the same rules apply as defined for parameterized tests above. In the following example, two arguments are injected into the constructor of the test class.

@ParameterizedClass
@CsvSource({ "apple, 23", "banana, 42" })
class FruitTests {

	final String fruit;
	final int quantity;

	FruitTests(String fruit, int quantity) {
		this.fruit = fruit;
		this.quantity = quantity;
	}

	@Test
	void test() {
		assertFruit(fruit);
		assertQuantity(quantity);
	}

	@Test
	void anotherTest() {
		// ...
	}
}

You may use records to implement parameterized classes that avoid the boilerplate code of declaring a test class constructor.

@ParameterizedClass
@CsvSource({ "apple, 23", "banana, 42" })
record FruitTests(String fruit, int quantity) {

	@Test
	void test() {
		assertFruit(fruit);
		assertQuantity(quantity);
	}

	@Test
	void anotherTest() {
		// ...
	}
}

Field Injection

For field injection, the following rules apply for fields annotated with @Parameter.

  • Zero or more indexed parameters may be declared; each must have a unique index specified in its @Parameter(index) annotation. The index may be omitted if there is only one indexed parameter. If there are at least two indexed parameter declarations, there must be declarations for all indexes from 0 to the largest declared index.

  • Zero or more aggregators may be declared; each without specifying an index in its @Parameter annotation.

  • Zero or more other fields may be declared as usual as long as they’re not annotated with @Parameter.

In this context, an indexed parameter is an argument for a given index in the Arguments provided by an ArgumentsProvider that is injected into a field annotated with @Parameter(index). An aggregator is any @Parameter-annotated field of type ArgumentsAccessor or any field annotated with @AggregateWith.

The following example demonstrates how to use field injection to consume multiple arguments in a parameterized class.

@ParameterizedClass
@CsvSource({ "apple, 23", "banana, 42" })
class FruitTests {

	@Parameter(0)
	String fruit;

	@Parameter(1)
	int quantity;

	@Test
	void test() {
		assertFruit(fruit);
		assertQuantity(quantity);
	}

	@Test
	void anotherTest() {
		// ...
	}
}

If field injection is used, no constructor parameters will be resolved with arguments from the source. Other ParameterResolver extensions may resolve constructor parameters as usual, though.

Lifecycle Methods

@BeforeParameterizedClassInvocation and @AfterParameterizedClassInvocation can also be used to consume arguments if their injectArguments attribute is set to true (the default). If so, their method signatures must follow the same rules apply as defined for parameterized tests and additionally use the same parameter types as the indexed parameters of the parameterized test class. Please refer to the Javadoc of @BeforeParameterizedClassInvocation and @AfterParameterizedClassInvocation for details and to the Lifecycle section for an example.

AutoCloseable arguments

Arguments that implement java.lang.AutoCloseable (or java.io.Closeable which extends java.lang.AutoCloseable) will be automatically closed after the parameterized class or test invocation.

To prevent this from happening, set the autoCloseArguments attribute in @ParameterizedTest to false. Specifically, if an argument that implements AutoCloseable is reused for multiple invocations of the same parameterized class or test method, you must specify the autoCloseArguments = false on the @ParameterizedClass or @ParameterizedTest annotation to ensure that the argument is not closed between invocations.

Other Extensions

Other extensions can access the parameters and resolved arguments of a parameterized test or class by retrieving a ParameterInfo object from the Store. Please refer to the Javadoc of ParameterInfo for details.

Sources of Arguments

Out of the box, JUnit Jupiter provides quite a few source annotations. Each of the following subsections provides a brief overview and an example for each of them. Please refer to the Javadoc in the org.junit.jupiter.params.provider package for additional information.

All source annotations in this section are applicable to both @ParameterizedClass and @ParameterizedTest. For the sake of brevity, the examples in this section will only show how to use them with @ParameterizedTest methods.

@ValueSource

@ValueSource is one of the simplest possible sources. It lets you specify a single array of literal values and can only be used for providing a single argument per parameterized test invocation.

The following types of literal values are supported by @ValueSource.

  • short

  • byte

  • int

  • long

  • float

  • double

  • char

  • boolean

  • java.lang.String

  • java.lang.Class

For example, the following @ParameterizedTest method will be invoked three times, with the values 1, 2, and 3 respectively.

@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
	assertTrue(argument > 0 && argument < 4);
}

Null and Empty Sources

In order to check corner cases and verify proper behavior of our software when it is supplied bad input, it can be useful to have null and empty values supplied to our parameterized tests. The following annotations serve as sources of null and empty values for parameterized tests that accept a single argument.

  • @NullSource: provides a single null argument to the annotated @ParameterizedClass or @ParameterizedTest.

    • @NullSource cannot be used for a parameter that has a primitive type.

  • @EmptySource: provides a single empty argument to the annotated @ParameterizedClass or @ParameterizedTest for parameters of the following types: java.lang.String, java.util.Collection (and concrete subtypes with a public no-arg constructor), java.util.List, java.util.Set, java.util.SortedSet, java.util.NavigableSet, java.util.Map (and concrete subtypes with a public no-arg constructor), java.util.SortedMap, java.util.NavigableMap, primitive arrays (e.g., int[], char[][], etc.), object arrays (e.g., String[], Integer[][], etc.).

  • @NullAndEmptySource: a composed annotation that combines the functionality of @NullSource and @EmptySource.

If you need to supply multiple varying types of blank strings to a parameterized class or test, you can achieve that using @ValueSource — for example, @ValueSource(strings = {" ", "   ", "\t", "\n"}).

You can also combine @NullSource, @EmptySource, and @ValueSource to test a wider range of null, empty, and blank input. The following example demonstrates how to achieve this for strings.

@ParameterizedTest
@NullSource
@EmptySource
@ValueSource(strings = { " ", "   ", "\t", "\n" })
void nullEmptyAndBlankStrings(String text) {
	assertTrue(text == null || text.isBlank());
}

Making use of the composed @NullAndEmptySource annotation simplifies the above as follows.

@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = { " ", "   ", "\t", "\n" })
void nullEmptyAndBlankStrings(String text) {
	assertTrue(text == null || text.isBlank());
}
Both variants of the nullEmptyAndBlankStrings(String) parameterized test method result in six invocations: 1 for null, 1 for the empty string, and 4 for the explicit blank strings supplied via @ValueSource.

@EnumSource

@EnumSource provides a convenient way to use Enum constants.

@ParameterizedTest
@EnumSource(ChronoUnit.class)
void testWithEnumSource(TemporalUnit unit) {
	assertNotNull(unit);
}

The annotation’s value attribute is optional. When omitted, the declared type of the first parameter is used. The test will fail if it does not reference an enum type. Thus, the value attribute is required in the above example because the method parameter is declared as TemporalUnit, i.e. the interface implemented by ChronoUnit, which isn’t an enum type. Changing the method parameter type to ChronoUnit allows you to omit the explicit enum type from the annotation as follows.

@ParameterizedTest
@EnumSource
void testWithEnumSourceWithAutoDetection(ChronoUnit unit) {
	assertNotNull(unit);
}

The annotation provides an optional names attribute that lets you specify which constants shall be used, like in the following example.

@ParameterizedTest
@EnumSource(names = { "DAYS", "HOURS" })
void testWithEnumSourceInclude(ChronoUnit unit) {
	assertTrue(EnumSet.of(ChronoUnit.DAYS, ChronoUnit.HOURS).contains(unit));
}

In addition to names, you can use the from and to attributes to specify a range of constants. The range starts from the constant specified in the from attribute and includes all subsequent constants up to and including the one specified in the to attribute, based on the natural order of the enum constants.

If from and to attributes are omitted, they default to the first and last constants in the enum type, respectively. If all names, from, and to attributes are omitted, all constants will be used. The following example demonstrates how to specify a range of constants.

@ParameterizedTest
@EnumSource(from = "HOURS", to = "DAYS")
void testWithEnumSourceRange(ChronoUnit unit) {
	assertTrue(EnumSet.of(ChronoUnit.HOURS, ChronoUnit.HALF_DAYS, ChronoUnit.DAYS).contains(unit));
}

The @EnumSource annotation also provides an optional mode attribute that enables fine-grained control over which constants are passed to the test method. For example, you can exclude names from the enum constant pool or specify regular expressions as in the following examples.

@ParameterizedTest
@EnumSource(mode = EXCLUDE, names = { "ERAS", "FOREVER" })
void testWithEnumSourceExclude(ChronoUnit unit) {
	assertFalse(EnumSet.of(ChronoUnit.ERAS, ChronoUnit.FOREVER).contains(unit));
}
@ParameterizedTest
@EnumSource(mode = MATCH_ALL, names = "^.*DAYS$")
void testWithEnumSourceRegex(ChronoUnit unit) {
	assertTrue(unit.name().endsWith("DAYS"));
}

You can also combine mode with the from, to and names attributes to define a range of constants while excluding specific values from that range as shown below.

@ParameterizedTest
@EnumSource(from = "HOURS", to = "DAYS", mode = EXCLUDE, names = { "HALF_DAYS" })
void testWithEnumSourceRangeExclude(ChronoUnit unit) {
	assertTrue(EnumSet.of(ChronoUnit.HOURS, ChronoUnit.DAYS).contains(unit));
	assertFalse(EnumSet.of(ChronoUnit.HALF_DAYS).contains(unit));
}

@MethodSource

@MethodSource allows you to refer to one or more factory methods of the test class or external classes.

Factory methods within the test class must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS); whereas, factory methods in external classes must always be static.

Each factory method must generate a stream of arguments, and each set of arguments within the stream will be provided as the physical arguments for individual invocations of the annotated @ParameterizedClass or @ParameterizedTest. Generally speaking this translates to a Stream of Arguments (i.e., Stream<Arguments>); however, the actual concrete return type can take on many forms. In this context, a "stream" is anything that JUnit can reliably convert into a Stream, such as Stream, DoubleStream, LongStream, IntStream, Collection, Iterator, Iterable, an array of objects or primitives, or any type that provides an iterator(): Iterator method (such as, for example, a kotlin.sequences.Sequence). The "arguments" within the stream can be supplied as an instance of Arguments, an array of objects (e.g., Object[]), or a single value if the parameterized class or test method accepts a single argument.

If the return type is Stream or one of the primitive streams, JUnit will properly close it by calling BaseStream.close(), making it safe to use a resource such as Files.lines().

If you only need a single parameter, you can return a Stream of instances of the parameter type as demonstrated in the following example.

@ParameterizedTest
@MethodSource("stringProvider")
void testWithExplicitLocalMethodSource(String argument) {
	assertNotNull(argument);
}

static Stream<String> stringProvider() {
	return Stream.of("apple", "banana");
}

For a @ParameterizedClass, providing a factory method name via @MethodSource is mandatory. For a @ParameterizedTest, if you do not explicitly provide a factory method name, JUnit Jupiter will search for a factory method with the same name as the current @ParameterizedTest method by convention. This is demonstrated in the following example.

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

static Stream<String> testWithDefaultLocalMethodSource() {
	return Stream.of("apple", "banana");
}

Streams for primitive types (DoubleStream, IntStream, and LongStream) are also supported as demonstrated by the following example.

@ParameterizedTest
@MethodSource("range")
void testWithRangeMethodSource(int argument) {
	assertNotEquals(9, argument);
}

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

If a parameterized class or test method declares multiple parameters, you need to return a collection, stream, or array of Arguments instances or object arrays as shown below (see the Javadoc for @MethodSource for further details on supported return types). Note that arguments(Object…​) is a static factory method defined in the Arguments interface. In addition, Arguments.of(Object…​) may be used as an alternative to arguments(Object…​).

@ParameterizedTest
@MethodSource("stringIntAndListProvider")
void testWithMultiArgMethodSource(String str, int num, List<String> list) {
	assertEquals(5, str.length());
	assertTrue(num >=1 && num <=2);
	assertEquals(2, list.size());
}

static Stream<Arguments> stringIntAndListProvider() {
	return Stream.of(
		arguments("apple", 1, Arrays.asList("a", "b")),
		arguments("lemon", 2, Arrays.asList("x", "y"))
	);
}

An external, static factory method can be referenced by providing its fully qualified method name as demonstrated in the following example.

package example;

import java.util.stream.Stream;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

class ExternalMethodSourceDemo {

	@ParameterizedTest
	@MethodSource("example.StringsProviders#tinyStrings")
	void testWithExternalMethodSource(String tinyString) {
		// test with tiny string
	}
}

class StringsProviders {

	static Stream<String> tinyStrings() {
		return Stream.of(".", "oo", "OOO");
	}
}

Factory methods can declare parameters, which will be provided by registered implementations of the ParameterResolver extension API. In the following example, the factory method is referenced by its name since there is only one such method in the test class. If there are several local methods with the same name, parameters can also be provided to differentiate them – for example, @MethodSource("factoryMethod()") or @MethodSource("factoryMethod(java.lang.String)"). Alternatively, the factory method can be referenced by its fully qualified method name, e.g. @MethodSource("example.MyTests#factoryMethod(java.lang.String)").

@RegisterExtension
static final IntegerResolver integerResolver = new IntegerResolver();

@ParameterizedTest
@MethodSource("factoryMethodWithArguments")
void testWithFactoryMethodWithArguments(String argument) {
	assertTrue(argument.startsWith("2"));
}

static Stream<Arguments> factoryMethodWithArguments(int quantity) {
	return Stream.of(
			arguments(quantity + " apples"),
			arguments(quantity + " lemons")
	);
}

static class IntegerResolver implements ParameterResolver {

	@Override
	public boolean supportsParameter(ParameterContext parameterContext,
			ExtensionContext extensionContext) {

		return parameterContext.getParameter().getType() == int.class;
	}

	@Override
	public Object resolveParameter(ParameterContext parameterContext,
			ExtensionContext extensionContext) {

		return 2;
	}

}

@FieldSource

@FieldSource allows you to refer to one or more fields of the test class or external classes.

Fields within the test class must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS); whereas, fields in external classes must always be static.

Each field must be able to supply a stream of arguments, and each set of "arguments" within the "stream" will be provided as the physical arguments for individual invocations of the annotated @ParameterizedClass or @ParameterizedTest.

In this context, a "stream" is anything that JUnit can reliably convert to a Stream; however, the actual concrete field type can take on many forms. Generally speaking this translates to a Collection, an Iterable, a Supplier of a stream (Stream, DoubleStream, LongStream, or IntStream), a Supplier of an Iterator, an array of objects or primitives, or any type that provides an iterator(): Iterator method (such as, for example, a kotlin.sequences.Sequence). Each set of "arguments" within the "stream" can be supplied as an instance of Arguments, an array of objects (for example, Object[], String[], etc.), or a single value if the parameterized class or test method accepts a single argument.

In contrast to the supported return types for @MethodSource factory methods, the value of a @FieldSource field cannot be an instance of Stream, DoubleStream, LongStream, IntStream, or Iterator, since the values of such types are consumed the first time they are processed. However, if you wish to use one of these types, you can wrap it in a Supplier — for example, Supplier<IntStream>.

If the Supplier return type is Stream or one of the primitive streams, JUnit will properly close it by calling BaseStream.close(), making it safe to use a resource such as Files.lines().

Please note that a one-dimensional array of objects supplied as a set of "arguments" will be handled differently than other types of arguments. Specifically, all the elements of a one-dimensional array of objects will be passed as individual physical arguments to the @ParameterizedClass or @ParameterizedTest. See the Javadoc for @FieldSource for further details.

For a @ParameterizedClass, providing a field name via @FieldSource is mandatory. For a @ParameterizedTest, if you do not explicitly provide a field name, JUnit Jupiter will search in the test class for a field that has the same name as the current @ParameterizedTest method by convention. This is demonstrated in the following example. This parameterized test method will be invoked twice: with the values "apple" and "banana".

@ParameterizedTest
@FieldSource
void arrayOfFruits(String fruit) {
	assertFruit(fruit);
}

static final String[] arrayOfFruits = { "apple", "banana" };

The following example demonstrates how to provide a single explicit field name via @FieldSource. This parameterized test method will be invoked twice: with the values "apple" and "banana".

@ParameterizedTest
@FieldSource("listOfFruits")
void singleFieldSource(String fruit) {
	assertFruit(fruit);
}

static final List<String> listOfFruits = Arrays.asList("apple", "banana");

The following example demonstrates how to provide multiple explicit field names via @FieldSource. This example uses the listOfFruits field from the previous example as well as the additionalFruits field. Consequently, this parameterized test method will be invoked four times: with the values "apple", "banana", "cherry", and "dewberry".

@ParameterizedTest
@FieldSource({ "listOfFruits", "additionalFruits" })
void multipleFieldSources(String fruit) {
	assertFruit(fruit);
}

static final Collection<String> additionalFruits = Arrays.asList("cherry", "dewberry");

It is also possible to provide a Stream, DoubleStream, IntStream, LongStream, or Iterator as the source of arguments via a @FieldSource field as long as the stream or iterator is wrapped in a java.util.function.Supplier. The following example demonstrates how to provide a Supplier of a Stream of named arguments. This parameterized test method will be invoked twice: with the values "apple" and "banana" and with display names "Apple" and "Banana", respectively.

@ParameterizedTest
@FieldSource
void namedArgumentsSupplier(String fruit) {
	assertFruit(fruit);
}

static final Supplier<Stream<Arguments>> namedArgumentsSupplier = () -> Stream.of(
	arguments(named("Apple", "apple")),
	arguments(named("Banana", "banana"))
);

Note that arguments(Object…​) is a static factory method defined in the org.junit.jupiter.params.provider.Arguments interface.

Similarly, named(String, Object) is a static factory method defined in the org.junit.jupiter.api.Named interface.

If a parameterized class or test method declares multiple parameters, the corresponding @FieldSource field must be able to provide a collection, stream supplier, or array of Arguments instances or object arrays as shown below (see the Javadoc for @FieldSource for further details on supported types).

@ParameterizedTest
@FieldSource("stringIntAndListArguments")
void testWithMultiArgFieldSource(String str, int num, List<String> list) {
	assertEquals(5, str.length());
	assertTrue(num >=1 && num <=2);
	assertEquals(2, list.size());
}

static List<Arguments> stringIntAndListArguments = Arrays.asList(
	arguments("apple", 1, Arrays.asList("a", "b")),
	arguments("lemon", 2, Arrays.asList("x", "y"))
);

Note that arguments(Object…​) is a static factory method defined in the org.junit.jupiter.params.provider.Arguments interface.

An external, static @FieldSource field can be referenced by providing its fully qualified field name as demonstrated in the following example.

@ParameterizedTest
@FieldSource("example.FruitUtils#tropicalFruits")
void testWithExternalFieldSource(String tropicalFruit) {
	// test with tropicalFruit
}

@CsvSource

@CsvSource allows you to express argument lists as comma-separated values (i.e., CSV String literals). Each string provided via the value attribute in @CsvSource represents a CSV record and results in one invocation of the parameterized class or test. The first record may optionally be used to supply CSV headers (see the Javadoc for the useHeadersInDisplayName attribute for details and an example).

@ParameterizedTest
@CsvSource({
	"apple,         1",
	"banana,        2",
	"'lemon, lime', 0xF1",
	"strawberry,    700_000"
})
void testWithCsvSource(String fruit, int rank) {
	assertNotNull(fruit);
	assertNotEquals(0, rank);
}

The default delimiter is a comma (,), but you can use another character by setting the delimiter attribute. Alternatively, the delimiterString attribute allows you to use a String delimiter instead of a single character. However, both delimiter attributes cannot be set simultaneously.

By default, @CsvSource uses a single quote (') as its quote character, but this can be changed via the quoteCharacter attribute. See the 'lemon, lime' value in the example above and in the table below. An empty, quoted value ('') results in an empty String unless the emptyValue attribute is set; whereas, an entirely empty value is interpreted as a null reference. By specifying one or more nullValues, a custom value can be interpreted as a null reference (see the NIL example in the table below). An ArgumentConversionException is thrown if the target type of a null reference is a primitive type.

An unquoted empty value will always be converted to a null reference regardless of any custom values configured via the nullValues attribute.

Except within a quoted string, leading and trailing whitespace in a CSV column is trimmed by default. This behavior can be changed by setting the ignoreLeadingAndTrailingWhitespace attribute to true.

Example Input Resulting Argument List

@CsvSource({ "apple, banana" })

"apple", "banana"

@CsvSource({ "apple, 'lemon, lime'" })

"apple", "lemon, lime"

@CsvSource({ "apple, ''" })

"apple", ""

@CsvSource({ "apple, " })

"apple", null

@CsvSource(value = { "apple, banana, NIL" }, nullValues = "NIL")

"apple", "banana", null

@CsvSource(value = { " apple , banana" }, ignoreLeadingAndTrailingWhitespace = false)

" apple ", " banana"

If the programming language you are using supports Java text blocks or equivalent multi-line string literals, you can alternatively use the textBlock attribute of @CsvSource. Each record within a text block represents a CSV record and results in one invocation of the parameterized class or test. The first record may optionally be used to supply CSV headers by setting the useHeadersInDisplayName attribute to true as in the example below.

Using a text block, the previous example can be implemented as follows.

@ParameterizedTest
@CsvSource(useHeadersInDisplayName = true, textBlock = """
	FRUIT,         RANK
	apple,         1
	banana,        2
	'lemon, lime', 0xF1
	strawberry,    700_000
	""")
void testWithCsvSource(String fruit, int rank) {
	// ...
}

The generated display names for the previous example include the CSV header names.

[1] FRUIT = "apple", RANK = "1"
[2] FRUIT = "banana", RANK = "2"
[3] FRUIT = "lemon, lime", RANK = "0xF1"
[4] FRUIT = "strawberry", RANK = "700_000"

In contrast to CSV records supplied via the value attribute, a text block can contain comments. Any line beginning with the value of the commentCharacter attribute (# by default) will be treated as a comment and ignored. Note that there is one exception to this rule: if the comment character appears within a quoted field, it loses its special meaning.

The comment character must be the first character on the line without any leading whitespace. It is therefore recommended that the closing text block delimiter (""") be placed either at the end of the last line of input or on the following line, left aligned with the rest of the input (as can be seen in the example below which demonstrates formatting similar to a table).

@ParameterizedTest
@CsvSource(delimiter = '|', quoteCharacter = '"', textBlock = """
	#-----------------------------
	#    FRUIT     |     RANK
	#-----------------------------
	     apple     |      1
	#-----------------------------
	     banana    |      2
	#-----------------------------
	  "lemon lime" |     0xF1
	#-----------------------------
	   strawberry  |    700_000
	#-----------------------------
	""")
void testWithCsvSource(String fruit, int rank) {
	// ...
}

Java’s text block feature automatically removes incidental whitespace when the code is compiled. However other JVM languages such as Groovy and Kotlin do not. Thus, if you are using a programming language other than Java and your text block contains comments or new lines within quoted strings, you will need to ensure that there is no leading whitespace within your text block.

@CsvFileSource

@CsvFileSource lets you use comma-separated value (CSV) files from the classpath or the local file system. Each record from a CSV file results in one invocation of the parameterized class or test. The first record may optionally be used to supply CSV headers. You can instruct JUnit to ignore the headers via the numLinesToSkip attribute. If you would like for the headers to be used in the display names, you can set the useHeadersInDisplayName attribute to true. The examples below demonstrate the use of numLinesToSkip and useHeadersInDisplayName.

The default delimiter is a comma (,), but you can use another character by setting the delimiter attribute. Alternatively, the delimiterString attribute allows you to use a String delimiter instead of a single character. However, both delimiter attributes cannot be set simultaneously.

Comments in CSV files
Any line beginning with the value of the commentCharacter attribute (# by default) will be interpreted as a comment and will be ignored.
@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSourceFromClasspath(String country, int reference) {
	assertNotNull(country);
	assertNotEquals(0, reference);
}

@ParameterizedTest
@CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSourceFromFile(String country, int reference) {
	assertNotNull(country);
	assertNotEquals(0, reference);
}

@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv", useHeadersInDisplayName = true)
void testWithCsvFileSourceAndHeaders(String country, int reference) {
	assertNotNull(country);
	assertNotEquals(0, reference);
}
two-column.csv
COUNTRY, REFERENCE
Sweden, 1
Poland, 2
"United States of America", 3
France, 700_000

The following listing shows the generated display names for the first two parameterized test methods above.

[1] country = "Sweden", reference = "1"
[2] country = "Poland", reference = "2"
[3] country = "United States of America", reference = "3"
[4] country = "France", reference = "700_000"

The following listing shows the generated display names for the last parameterized test method above that uses CSV header names.

[1] COUNTRY = "Sweden", REFERENCE = "1"
[2] COUNTRY = "Poland", REFERENCE = "2"
[3] COUNTRY = "United States of America", REFERENCE = "3"
[4] COUNTRY = "France", REFERENCE = "700_000"

In contrast to the default syntax used in @CsvSource, @CsvFileSource uses a double quote (") as the quote character by default, but this can be changed via the quoteCharacter attribute. See the "United States of America" value in the example above. An empty, quoted value ("") results in an empty String unless the emptyValue attribute is set; whereas, an entirely empty value is interpreted as a null reference. By specifying one or more nullValues, a custom value can be interpreted as a null reference. An ArgumentConversionException is thrown if the target type of a null reference is a primitive type.

An unquoted empty value will always be converted to a null reference regardless of any custom values configured via the nullValues attribute.

Except within a quoted string, leading and trailing whitespace in a CSV column is trimmed by default. This behavior can be changed by setting the ignoreLeadingAndTrailingWhitespace attribute to true.

@ArgumentsSource

@ArgumentsSource can be used to specify a custom, reusable ArgumentsProvider. Note that an implementation of ArgumentsProvider must be declared as either a top-level class or as a static nested class.

@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void testWithArgumentsSource(String argument) {
	assertNotNull(argument);
}
public class MyArgumentsProvider implements ArgumentsProvider {

	@Override
	public Stream<? extends Arguments> provideArguments(ParameterDeclarations parameters,
			ExtensionContext context) {
		return Stream.of("apple", "banana").map(Arguments::of);
	}
}

If you wish to implement a custom ArgumentsProvider that also consumes an annotation (like built-in providers such as ValueArgumentsProvider or CsvArgumentsProvider), you have the possibility to extend the AnnotationBasedArgumentsProvider class.

Moreover, ArgumentsProvider implementations may declare constructor parameters in case they need to be resolved by a registered ParameterResolver as demonstrated in the following example.

public class MyArgumentsProviderWithConstructorInjection implements ArgumentsProvider {

	private final TestInfo testInfo;

	public MyArgumentsProviderWithConstructorInjection(TestInfo testInfo) {
		this.testInfo = testInfo;
	}

	@Override
	public Stream<? extends Arguments> provideArguments(ParameterDeclarations parameters,
			ExtensionContext context) {
		return Stream.of(Arguments.of(testInfo.getDisplayName()));
	}
}

Multiple sources using repeatable annotations

Repeatable annotations provide a convenient way to specify multiple sources from different providers.

@DisplayName("A parameterized test that makes use of repeatable annotations")
@ParameterizedTest
@MethodSource("someProvider")
@MethodSource("otherProvider")
void testWithRepeatedAnnotation(String argument) {
	assertNotNull(argument);
}

static Stream<String> someProvider() {
	return Stream.of("foo");
}

static Stream<String> otherProvider() {
	return Stream.of("bar");
}

Following the above parameterized test, a test case will run for each argument:

[1] foo
[2] bar

The following annotations are repeatable:

  • @ValueSource

  • @EnumSource

  • @MethodSource

  • @FieldSource

  • @CsvSource

  • @CsvFileSource

  • @ArgumentsSource

Argument Count Validation

By default, when an arguments source provides more arguments than the test method needs, those additional arguments are ignored and the test executes as usual. This can lead to bugs where arguments are never passed to the parameterized class or method.

To prevent this, you can set argument count validation to 'strict'. Then, any additional arguments will cause an error instead.

To change this behavior for all tests, set the junit.jupiter.params.argumentCountValidation configuration parameter to strict. To change this behavior for a single parameterized class or test method, use the argumentCountValidation attribute of the @ParameterizedClass or @ParameterizedTest annotation:

@ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.STRICT)
@CsvSource({ "42, -666" })
void testWithArgumentCountValidation(int number) {
	assertTrue(number > 0);
}

Argument Conversion

Widening Conversion

JUnit Jupiter supports Widening Primitive Conversion for arguments supplied to a @ParameterizedClass or @ParameterizedTest. For example, a parameterized class or test method annotated with @ValueSource(ints = { 1, 2, 3 }) can be declared to accept not only an argument of type int but also an argument of type long, float, or double.

Implicit Conversion

To support use cases like @CsvSource, JUnit Jupiter provides a number of built-in implicit type converters. The conversion process depends on the declared type of each method parameter.

For example, if a @ParameterizedClass or @ParameterizedTest declares a parameter of type TimeUnit and the actual type supplied by the declared source is a String, the string will be automatically converted into the corresponding TimeUnit enum constant.

@ParameterizedTest
@ValueSource(strings = "SECONDS")
void testWithImplicitArgumentConversion(ChronoUnit argument) {
	assertNotNull(argument.name());
}

String instances are implicitly converted to the following target types.

Decimal, hexadecimal, and octal String literals will be converted to their integral types: byte, short, int, long, and their boxed counterparts.
Target Type Example

boolean/Boolean

"true"true (only accepts values 'true' or 'false', case-insensitive)

byte/Byte

"15", "0xF", or "017"(byte) 15

char/Character

"o"'o'

short/Short

"15", "0xF", or "017"(short) 15

int/Integer

"15", "0xF", or "017"15

long/Long

"15", "0xF", or "017"15L

float/Float

"1.0"1.0f

double/Double

"1.0"1.0d

Enum subclass

"SECONDS"TimeUnit.SECONDS

java.io.File

"/path/to/file"new File("/path/to/file")

java.lang.Class

"java.lang.Integer"java.lang.Integer.class (use $ for nested classes, e.g. "java.lang.Thread$State")

java.lang.Class

"byte"byte.class (primitive types are supported)

java.lang.Class

"char[]"char[].class (array types are supported)

java.math.BigDecimal

"123.456e789"new BigDecimal("123.456e789")

java.math.BigInteger

"1234567890123456789"new BigInteger("1234567890123456789")

java.net.URI

"https://junit.org/"URI.create("https://junit.org/")

java.net.URL

"https://junit.org/"URI.create("https://junit.org/").toURL()

java.nio.charset.Charset

"UTF-8"Charset.forName("UTF-8")

java.nio.file.Path

"/path/to/file"Paths.get("/path/to/file")

java.time.Duration

"PT3S"Duration.ofSeconds(3)

java.time.Instant

"1970-01-01T00:00:00Z"Instant.ofEpochMilli(0)

java.time.LocalDateTime

"2017-03-14T12:34:56.789"LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000)

java.time.LocalDate

"2017-03-14"LocalDate.of(2017, 3, 14)

java.time.LocalTime

"12:34:56.789"LocalTime.of(12, 34, 56, 789_000_000)

java.time.MonthDay

"--03-14"MonthDay.of(3, 14)

java.time.OffsetDateTime

"2017-03-14T12:34:56.789Z"OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)

java.time.OffsetTime

"12:34:56.789Z"OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC)

java.time.Period

"P2M6D"Period.of(0, 2, 6)

java.time.YearMonth

"2017-03"YearMonth.of(2017, 3)

java.time.Year

"2017"Year.of(2017)

java.time.ZonedDateTime

"2017-03-14T12:34:56.789Z"ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)

java.time.ZoneId

"Europe/Berlin"ZoneId.of("Europe/Berlin")

java.time.ZoneOffset

"+02:30"ZoneOffset.ofHoursMinutes(2, 30)

java.util.Currency

"JPY"Currency.getInstance("JPY")

java.util.Locale

"en-US"Locale.forLanguageTag("en-US")

java.util.UUID

"d043e930-7b3b-48e3-bdbe-5a3ccfb833db"UUID.fromString("d043e930-7b3b-48e3-bdbe-5a3ccfb833db")

Fallback String-to-Object Conversion

In addition to implicit conversion from strings to the target types listed in the above table, JUnit Jupiter also provides a fallback mechanism for automatic conversion from a String to a given target type if the target type declares exactly one suitable factory method or a factory constructor as defined below.

  • factory method: a non-private, static method declared in the target type that accepts either a single String argument or a single CharSequence argument and returns an instance of the target type. The name of the method can be arbitrary and need not follow any particular convention.

  • factory constructor: a non-private constructor in the target type that accepts a either a single String argument or a single CharSequence argument. Note that the target type must be declared as either a top-level class or as a static nested class.

If multiple factory methods are discovered, they will be ignored. If a factory method and a factory constructor are discovered, the factory method will be used instead of the constructor.

For example, in the following @ParameterizedTest method, the Book argument will be created by invoking the Book.fromTitle(String) factory method and passing "42 Cats" as the title of the book.

@ParameterizedTest
@ValueSource(strings = "42 Cats")
void testWithImplicitFallbackArgumentConversion(Book book) {
	assertEquals("42 Cats", book.getTitle());
}
public class Book {

	private final String title;

	private Book(String title) {
		this.title = title;
	}

	public static Book fromTitle(String title) {
		return new Book(title);
	}

	public String getTitle() {
		return this.title;
	}
}

Explicit Conversion

Instead of relying on implicit argument conversion, you may explicitly specify an ArgumentConverter to use for a certain parameter using the @ConvertWith annotation like in the following example. Note that an implementation of ArgumentConverter must be declared as either a top-level class or as a static nested class.

@ParameterizedTest
@EnumSource(ChronoUnit.class)
void testWithExplicitArgumentConversion(
		@ConvertWith(ToStringArgumentConverter.class) String argument) {

	assertNotNull(ChronoUnit.valueOf(argument));
}
public class ToStringArgumentConverter extends SimpleArgumentConverter {

	@Override
	protected Object convert(Object source, Class<?> targetType) {
		assertEquals(String.class, targetType, "Can only convert to String");
		if (source instanceof Enum<?> constant) {
			return constant.name();
		}
		return String.valueOf(source);
	}
}

If the converter is only meant to convert one type to another, you can extend TypedArgumentConverter to avoid boilerplate type checks.

public class ToLengthArgumentConverter extends TypedArgumentConverter<String, Integer> {

	protected ToLengthArgumentConverter() {
		super(String.class, Integer.class);
	}

	@Override
	protected Integer convert(String source) {
		return (source != null ? source.length() : 0);
	}

}

Explicit argument converters are meant to be implemented by test and extension authors. Thus, junit-jupiter-params only provides a single explicit argument converter that may also serve as a reference implementation: JavaTimeArgumentConverter. It is used via the composed annotation JavaTimeConversionPattern.

@ParameterizedTest
@ValueSource(strings = { "01.01.2017", "31.12.2017" })
void testWithExplicitJavaTimeConverter(
		@JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) {

	assertEquals(2017, argument.getYear());
}

If you wish to implement a custom ArgumentConverter that also consumes an annotation (like JavaTimeArgumentConverter), you have the possibility to extend the AnnotationBasedArgumentConverter class.

Argument Aggregation

By default, each argument provided to a @ParameterizedClass or @ParameterizedTest corresponds to a single method parameter. Consequently, argument sources which are expected to supply a large number of arguments can lead to large constructor or method signatures, respectively.

In such cases, an ArgumentsAccessor can be used instead of multiple parameters. Using this API, you can access the provided arguments through a single argument passed to your test method. In addition, type conversion is supported as discussed in Implicit Conversion.

Besides, you can retrieve the current test invocation index with ArgumentsAccessor.getInvocationIndex().

@ParameterizedTest
@CsvSource({
	"Jane, Doe, F, 1990-05-20",
	"John, Doe, M, 1990-10-22"
})
void testWithArgumentsAccessor(ArgumentsAccessor arguments) {
	Person person = new Person(
								arguments.getString(0),
								arguments.getString(1),
								arguments.get(2, Gender.class),
								arguments.get(3, LocalDate.class));

	if (person.getFirstName().equals("Jane")) {
		assertEquals(Gender.F, person.getGender());
	}
	else {
		assertEquals(Gender.M, person.getGender());
	}
	assertEquals("Doe", person.getLastName());
	assertEquals(1990, person.getDateOfBirth().getYear());
}

An instance of ArgumentsAccessor is automatically injected into any parameter of type ArgumentsAccessor.

Custom Aggregators

Apart from direct access to the arguments of a @ParameterizedClass or @ParameterizedTest using an ArgumentsAccessor, JUnit Jupiter also supports the usage of custom, reusable aggregators.

To use a custom aggregator, implement the ArgumentsAggregator interface and register it via the @AggregateWith annotation on a compatible parameter of the @ParameterizedClass or @ParameterizedTest. The result of the aggregation will then be provided as an argument for the corresponding parameter when the parameterized test is invoked. Note that an implementation of ArgumentsAggregator must be declared as either a top-level class or as a static nested class.

@ParameterizedTest
@CsvSource({
	"Jane, Doe, F, 1990-05-20",
	"John, Doe, M, 1990-10-22"
})
void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) {
	// perform assertions against person
}
public class PersonAggregator extends SimpleArgumentsAggregator {
	@Override
	protected Person aggregateArguments(ArgumentsAccessor arguments, Class<?> targetType,
			AnnotatedElementContext context, int parameterIndex) {
		return new Person(
							arguments.getString(0),
							arguments.getString(1),
							arguments.get(2, Gender.class),
							arguments.get(3, LocalDate.class));
	}
}

If you find yourself repeatedly declaring @AggregateWith(MyTypeAggregator.class) for multiple parameterized classes or methods across your codebase, you may wish to create a custom composed annotation such as @CsvToMyType that is meta-annotated with @AggregateWith(MyTypeAggregator.class). The following example demonstrates this in action with a custom @CsvToPerson annotation.

@ParameterizedTest
@CsvSource({
	"Jane, Doe, F, 1990-05-20",
	"John, Doe, M, 1990-10-22"
})
void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) {
	// perform assertions against person
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AggregateWith(PersonAggregator.class)
public @interface CsvToPerson {
}

Customizing Display Names

By default, the display name of a parameterized class or test invocation contains the invocation index and a comma-separated list of the String representations of all arguments for that specific invocation. If parameter names are present in the bytecode, each argument will be preceded by its parameter name and an equals sign (unless the argument is only available via an ArgumentsAccessor or ArgumentAggregator) – for example, firstName = "Jane".

To ensure that parameter names are present in the bytecode, test code must be compiled with the -parameters compiler flag for Java or with the -java-parameters compiler flag for Kotlin.

However, you can customize invocation display names via the name attribute of the @ParameterizedClass or @ParameterizedTest annotation as in the following example.

@DisplayName("Display name of container")
@ParameterizedTest(name = "{index} ==> the rank of {0} is {1}")
@CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 3" })
void testWithCustomDisplayNames(String fruit, int rank) {
}

When executing the above method using the ConsoleLauncher you will see output similar to the following.

Display name of container ✔
├─ 1 ==> the rank of "apple" is "1" ✔
├─ 2 ==> the rank of "banana" is "2" ✔
└─ 3 ==> the rank of "lemon, lime" is "3" ✔

Please note that name is a MessageFormat pattern. Thus, a single quote (') needs to be represented as a doubled single quote ('') in order to be displayed.

The following placeholders are supported within custom display names.

Placeholder Description

{displayName}

the display name of the method

{index}

the current invocation index (1-based)

{arguments}

the complete, comma-separated arguments list

{argumentsWithNames}

the complete, comma-separated arguments list with parameter names

{argumentSetName}

the name of the argument set

{argumentSetNameOrArgumentsWithNames}

{argumentSetName} or {argumentsWithNames}, depending on how the arguments are supplied

{0}, {1}, …​

an individual argument

When including arguments in display names, their string representations are truncated if they exceed the configured maximum length. The limit is configurable via the junit.jupiter.params.displayname.argument.maxlength configuration parameter and defaults to 512 characters.

When using @MethodSource, @FieldSource, or @ArgumentsSource, you can provide custom names for individual arguments or custom names for entire sets of arguments.

Use the Named API to provide a custom name for an individual argument, and the custom name will be used if the argument is included in the invocation display name, like in the example below.

@DisplayName("A parameterized test with named arguments")
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("namedArguments")
void testWithNamedArguments(File file) {
}

static Stream<Arguments> namedArguments() {
	return Stream.of(
		arguments(named("An important file", new File("path1"))),
		arguments(named("Another file", new File("path2")))
	);
}

When executing the above method using the ConsoleLauncher you will see output similar to the following.

A parameterized test with named arguments ✔
├─ 1: An important file ✔
└─ 2: Another file ✔

Note that arguments(Object…​) is a static factory method defined in the org.junit.jupiter.params.provider.Arguments interface.

Similarly, named(String, Object) is a static factory method defined in the org.junit.jupiter.api.Named interface.

Use the ArgumentSet API to provide a custom name for the entire set of arguments, and the custom name will be used as the display name, like in the example below.

@DisplayName("A parameterized test with named argument sets")
@ParameterizedTest
@FieldSource("argumentSets")
void testWithArgumentSets(File file1, File file2) {
}

static List<Arguments> argumentSets = Arrays.asList(
	argumentSet("Important files", new File("path1"), new File("path2")),
	argumentSet("Other files", new File("path3"), new File("path4"))
);

When executing the above method using the ConsoleLauncher you will see output similar to the following.

A parameterized test with named argument sets ✔
├─ [1] Important files ✔
└─ [2] Other files ✔

Note that argumentSet(String, Object…​) is a static factory method defined in the org.junit.jupiter.params.provider.Arguments interface.

Quoted Text-based Arguments

As of JUnit Jupiter 6.0, text-based arguments in display names for parameterized tests are quoted by default. In this context, any CharSequence (such as a String) or Character is considered text. A CharSequence is wrapped in double quotes ("), and a Character is wrapped in single quotes (').

Special characters will be escaped in the quoted text. For example, carriage returns and line feeds will be escaped as \\r and \\n, respectively.

This feature can be disabled by setting the quoteTextArguments attributes in @ParameterizedClass and @ParameterizedTest to false.

For example, given a string argument "line 1\nline 2", the physical representation in the display name will be "\"line 1\\nline 2\"" which is printed as "line 1\nline 2". Similarly, given a string argument "\t", the physical representation in the display name will be "\"\\t\"" which is printed as "\t" instead of a blank string or invisible tab character. The same applies for a character argument '\t', whose physical representation in the display name would be "'\\t'" which is printed as '\t'.

For a concrete example, if you run the first nullEmptyAndBlankStrings(String text) parameterized test method from the Null and Empty Sources section above, the following display names are generated.

[1] text = null
[2] text = ""
[3] text = " "
[4] text = "   "
[5] text = "\t"
[6] text = "\n"

If you run the first testWithCsvSource(String fruit, int rank) parameterized test method from the @CsvSource section above, the following display names are generated.

[1] fruit = "apple", rank = "1"
[2] fruit = "banana", rank = "2"
[3] fruit = "lemon, lime", rank = "0xF1"
[4] fruit = "strawberry", rank = "700_000"

The original source arguments are quoted when generating a display name, and this occurs before any implicit or explicit argument conversion is performed.

For example, if a parameterized test accepts 3.14 as a float argument that was converted from "3.14" as an input string, "3.14" will be present in the display name instead of 3.14. You can see the effect of this with the rank values in the above example.

Default Display Name Pattern

If you’d like to set a default name pattern for all parameterized classes and tests in your project, you can declare the junit.jupiter.params.displayname.default configuration parameter in the junit-platform.properties file as demonstrated in the following example (see Configuration Parameters for other options).

junit.jupiter.params.displayname.default = {index}

Precedence Rules

The display name for a parameterized class or test is determined according to the following precedence rules:

  1. name attribute in @ParameterizedClass or @ParameterizedTest, if present

  2. value of the junit.jupiter.params.displayname.default configuration parameter, if present

  3. DEFAULT_DISPLAY_NAME constant defined in org.junit.jupiter.params.ParameterizedInvocationConstants

Lifecycle and Interoperability

Parameterized Tests

Each invocation of a parameterized test has the same lifecycle as a regular @Test method. For example, @BeforeEach methods will be executed before each invocation. Similar to Dynamic Tests, invocations will appear one by one in the test tree of an IDE. You may at will mix regular @Test methods and @ParameterizedTest methods within the same test class.

You may use ParameterResolver extensions with @ParameterizedTest methods. However, method parameters that are resolved by argument sources need to come first in the parameter list. Since a test class may contain regular tests as well as parameterized tests with different parameter lists, values from argument sources are not resolved for lifecycle methods (e.g. @BeforeEach) and test class constructors.

@BeforeEach
void beforeEach(TestInfo testInfo) {
	// ...
}

@ParameterizedTest
@ValueSource(strings = "apple")
void testWithRegularParameterResolver(String argument, TestReporter testReporter) {
	testReporter.publishEntry("argument", argument);
}

@AfterEach
void afterEach(TestInfo testInfo) {
	// ...
}

Parameterized Classes

Each invocation of a parameterized class has the same lifecycle as a regular test class. For example, @BeforeAll methods will be executed once before all invocations and @BeforeEach methods will be executed before each test method invocation. Similar to Dynamic Tests, invocations will appear one by one in the test tree of an IDE.

You may use ParameterResolver extensions with @ParameterizedClass constructors. However, if constructor injection is used, constructor parameters that are resolved by argument sources need to come first in the parameter list. Values from argument sources are not resolved for regular lifecycle methods (e.g. @BeforeEach).

In addition to regular lifecycle methods, parameterized classes may declare @BeforeParameterizedClassInvocation and @AfterParameterizedClassInvocation lifecycle methods that are called once before/after each invocation of the parameterized class. These methods must be static unless the parameterized class is configured to use @TestInstance(Lifecycle.PER_CLASS) (see Test Instance Lifecycle).

These lifecycle methods may optionally declare parameters that are resolved depending on the setting of the injectArguments annotation attribute. If it is set to false, the parameters must be resolved by other registered ParameterResolver extensions. If the attribute is set to true (the default), the method may declare parameters that match the arguments of the parameterized class (see the Javadoc of @BeforeParameterizedClassInvocation and @AfterParameterizedClassInvocation for details). This may, for example, be used to initialize the used arguments as demonstrated by the following example.

Using parameterized class lifecycle methods
@ParameterizedClass
@MethodSource("textFiles")
class TextFileTests {

	static List<TextFile> textFiles() {
		return List.of(
			new TextFile("file1", "first content"),
			new TextFile("file2", "second content")
		);
	}

	@Parameter
	TextFile textFile;

	@BeforeParameterizedClassInvocation
	static void beforeInvocation(TextFile textFile, @TempDir Path tempDir) throws Exception {
		var filePath = tempDir.resolve(textFile.fileName); (1)
		textFile.path = Files.writeString(filePath, textFile.content);
	}

	@SuppressWarnings("DataFlowIssue")
	@AfterParameterizedClassInvocation
	static void afterInvocation(TextFile textFile) throws Exception {
		var actualContent = Files.readString(textFile.path); (3)
		assertEquals(textFile.content, actualContent, "Content must not have changed");
		// Custom cleanup logic, if necessary
		// File will be deleted automatically by @TempDir support
	}

	@SuppressWarnings("DataFlowIssue")
	@Test
	void test() {
		assertTrue(Files.exists(textFile.path)); (2)
	}

	@Test
	void anotherTest() {
		// ...
	}

	static class TextFile {

		final String fileName;
		final String content;
		Path path;

		TextFile(String fileName, String content) {
			this.fileName = fileName;
			this.content = content;
		}

		@Override
		public String toString() {
			return fileName;
		}
	}
}
1 Initialization of the argument before each invocation of the parameterized class
2 Usage of the previously initialized argument in a test method
3 Validation and cleanup of the argument after each invocation of the parameterized class