2.15. Parameterized Tests

Parameterized tests make it possible to run a test multiple times with different arguments. They are declared just like regular @Test methods but use the [@ParameterizedTest](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedTest.html) annotation instead. In addition, you must declare at least one source that will provide the arguments for each invocation and then consume the arguments in the test method.

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

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

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

  1. palindromes(String)
  2. ├─ [1] candidate=racecar
  3. ├─ [2] candidate=radar
  4. └─ [3] candidate=able was I ere I saw elba
Parameterized tests are currently an experimental feature. Consult the table in Experimental APIs for details.

2.15.1. Required Setup

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

2.15.2. Consuming Arguments

Parameterized test methods typically 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 arguments 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 argument 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.

2.15.3. 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](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/package-summary.html) package for additional information.

@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.

  1. @ParameterizedTest
  2. @ValueSource(ints = { 1, 2, 3 })
  3. void testWithValueSource(int argument) {
  4. assertTrue(argument > 0 && argument < 4);
  5. }
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](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/NullSource.html): provides a single null argument to the annotated @ParameterizedTest method.

    • @NullSource cannot be used for a parameter that has a primitive type.
  • [@EmptySource](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/EmptySource.html): provides a single empty argument to the annotated @ParameterizedTest method for parameters of the following types: java.lang.String, java.util.List, java.util.Set, java.util.Map, primitive arrays (e.g., int[], char[][], etc.), object arrays (e.g.,String[], Integer[][], etc.).

    • Subtypes of the supported types are not supported.
  • [@NullAndEmptySource](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/NullAndEmptySource.html): 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 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.

  1. @ParameterizedTest
  2. @NullSource
  3. @EmptySource
  4. @ValueSource(strings = { " ", " ", "\t", "\n" })
  5. void nullEmptyAndBlankStrings(String text) {
  6. assertTrue(text == null || text.trim().isEmpty());
  7. }

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

  1. @ParameterizedTest
  2. @NullAndEmptySource
  3. @ValueSource(strings = { " ", " ", "\t", "\n" })
  4. void nullEmptyAndBlankStrings(String text) {
  5. assertTrue(text == null || text.trim().isEmpty());
  6. }
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.

  1. @ParameterizedTest
  2. @EnumSource(ChronoUnit.class)
  3. void testWithEnumSource(TemporalUnit unit) {
  4. assertNotNull(unit);
  5. }

The annotation’s value attribute is optional. When omitted, the declared type of the first method 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.

  1. @ParameterizedTest
  2. @EnumSource
  3. void testWithEnumSourceWithAutoDetection(ChronoUnit unit) {
  4. assertNotNull(unit);
  5. }

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

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

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.

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

[@MethodSource](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/MethodSource.html) 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. In addition, such factory methods must not accept any arguments.

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 @ParameterizedTest method. 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 an array of primitives. 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 test method accepts a single argument.

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

  1. @ParameterizedTest
  2. @MethodSource("stringProvider")
  3. void testWithExplicitLocalMethodSource(String argument) {
  4. assertNotNull(argument);
  5. }
  6. static Stream<String> stringProvider() {
  7. return Stream.of("apple", "banana");
  8. }

If you do not explicitly provide a factory method name via @MethodSource, JUnit Jupiter will search for a factory method that has the same name as the current @ParameterizedTest method by convention. This is demonstrated in the following example.

  1. @ParameterizedTest
  2. @MethodSource
  3. void testWithDefaultLocalMethodSource(String argument) {
  4. assertNotNull(argument);
  5. }
  6. static Stream<String> testWithDefaultLocalMethodSource() {
  7. return Stream.of("apple", "banana");
  8. }

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

  1. @ParameterizedTest
  2. @MethodSource("range")
  3. void testWithRangeMethodSource(int argument) {
  4. assertNotEquals(9, argument);
  5. }
  6. static IntStream range() {
  7. return IntStream.range(0, 20).skip(10);
  8. }

If a parameterized 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](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/MethodSource.html) 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…​).

  1. @ParameterizedTest
  2. @MethodSource("stringIntAndListProvider")
  3. void testWithMultiArgMethodSource(String str, int num, List<String> list) {
  4. assertEquals(5, str.length());
  5. assertTrue(num >=1 && num <=2);
  6. assertEquals(2, list.size());
  7. }
  8. static Stream<Arguments> stringIntAndListProvider() {
  9. return Stream.of(
  10. arguments("apple", 1, Arrays.asList("a", "b")),
  11. arguments("lemon", 2, Arrays.asList("x", "y"))
  12. );
  13. }

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

  1. package example;
  2. import java.util.stream.Stream;
  3. import org.junit.jupiter.params.ParameterizedTest;
  4. import org.junit.jupiter.params.provider.MethodSource;
  5. class ExternalMethodSourceDemo {
  6. @ParameterizedTest
  7. @MethodSource("example.StringsProviders#tinyStrings")
  8. void testWithExternalMethodSource(String tinyString) {
  9. // test with tiny string
  10. }
  11. }
  12. class StringsProviders {
  13. static Stream<String> tinyStrings() {
  14. return Stream.of(".", "oo", "OOO");
  15. }
  16. }
@CsvSource

@CsvSource allows you to express argument lists as comma-separated values (i.e., String literals).

  1. @ParameterizedTest
  2. @CsvSource({
  3. "apple, 1",
  4. "banana, 2",
  5. "'lemon, lime', 0xF1"
  6. })
  7. void testWithCsvSource(String fruit, int rank) {
  8. assertNotNull(fruit);
  9. assertNotEquals(0, rank);
  10. }

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.

@CsvSource uses a single quote ' as its quote character. 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.
Example InputResulting 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

@CsvFileSource

@CsvFileSource lets you use CSV files from the classpath or the local file system. Each line from a CSV file results in one invocation of the parameterized test.

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 a # symbol will be interpreted as a comment and will be ignored.
  1. @ParameterizedTest
  2. @CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1)
  3. void testWithCsvFileSourceFromClasspath(String country, int reference) {
  4. assertNotNull(country);
  5. assertNotEquals(0, reference);
  6. }
  7. @ParameterizedTest
  8. @CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1)
  9. void testWithCsvFileSourceFromFile(String country, int reference) {
  10. assertNotNull(country);
  11. assertNotEquals(0, reference);
  12. }

two-column.csv

  1. Country, reference
  2. Sweden, 1
  3. Poland, 2
  4. "United States of America", 3

In contrast to the syntax used in @CsvSource, @CsvFileSource uses a double quote " as the quote character. 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.
@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.

  1. @ParameterizedTest
  2. @ArgumentsSource(MyArgumentsProvider.class)
  3. void testWithArgumentsSource(String argument) {
  4. assertNotNull(argument);
  5. }
  1. public class MyArgumentsProvider implements ArgumentsProvider {
  2. @Override
  3. public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
  4. return Stream.of("apple", "banana").map(Arguments::of);
  5. }
  6. }

2.15.4. Argument Conversion

Widening Conversion

JUnit Jupiter supports Widening Primitive Conversion for arguments supplied to a @ParameterizedTest. For example, a parameterized test 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 @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.

  1. @ParameterizedTest
  2. @ValueSource(strings = "SECONDS")
  3. void testWithImplicitArgumentConversion(ChronoUnit argument) {
  4. assertNotNull(argument.name());
  5. }

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 TypeExample

boolean/Boolean

“true”true

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/new URL(“https://junit.org/“)

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”new Locale(“en”)

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 a single String 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 single String 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.

  1. @ParameterizedTest
  2. @ValueSource(strings = "42 Cats")
  3. void testWithImplicitFallbackArgumentConversion(Book book) {
  4. assertEquals("42 Cats", book.getTitle());
  5. }
  1. public class Book {
  2. private final String title;
  3. private Book(String title) {
  4. this.title = title;
  5. }
  6. public static Book fromTitle(String title) {
  7. return new Book(title);
  8. }
  9. public String getTitle() {
  10. return this.title;
  11. }
  12. }
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.

  1. @ParameterizedTest
  2. @EnumSource(ChronoUnit.class)
  3. void testWithExplicitArgumentConversion(
  4. @ConvertWith(ToStringArgumentConverter.class) String argument) {
  5. assertNotNull(ChronoUnit.valueOf(argument));
  6. }
  1. public class ToStringArgumentConverter extends SimpleArgumentConverter {
  2. @Override
  3. protected Object convert(Object source, Class<?> targetType) {
  4. assertEquals(String.class, targetType, "Can only convert to String");
  5. if (source instanceof Enum<?>) {
  6. return ((Enum<?>) source).name();
  7. }
  8. return String.valueOf(source);
  9. }
  10. }

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

  1. public class ToLengthArgumentConverter extends TypedArgumentConverter<String, Integer> {
  2. protected ToLengthArgumentConverter() {
  3. super(String.class, Integer.class);
  4. }
  5. @Override
  6. protected Integer convert(String source) {
  7. return source.length();
  8. }
  9. }

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.

  1. @ParameterizedTest
  2. @ValueSource(strings = { "01.01.2017", "31.12.2017" })
  3. void testWithExplicitJavaTimeConverter(
  4. @JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) {
  5. assertEquals(2017, argument.getYear());
  6. }

2.15.5. Argument Aggregation

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

In such cases, an [ArgumentsAccessor](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/ArgumentsAccessor.html) 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.

  1. @ParameterizedTest
  2. @CsvSource({
  3. "Jane, Doe, F, 1990-05-20",
  4. "John, Doe, M, 1990-10-22"
  5. })
  6. void testWithArgumentsAccessor(ArgumentsAccessor arguments) {
  7. Person person = new Person(arguments.getString(0),
  8. arguments.getString(1),
  9. arguments.get(2, Gender.class),
  10. arguments.get(3, LocalDate.class));
  11. if (person.getFirstName().equals("Jane")) {
  12. assertEquals(Gender.F, person.getGender());
  13. }
  14. else {
  15. assertEquals(Gender.M, person.getGender());
  16. }
  17. assertEquals("Doe", person.getLastName());
  18. assertEquals(1990, person.getDateOfBirth().getYear());
  19. }

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

Custom Aggregators

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

To use a custom aggregator, implement the [ArgumentsAggregator](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/ArgumentsAggregator.html) interface and register it via the @AggregateWith annotation on a compatible parameter in the @ParameterizedTest method. 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.

  1. @ParameterizedTest
  2. @CsvSource({
  3. "Jane, Doe, F, 1990-05-20",
  4. "John, Doe, M, 1990-10-22"
  5. })
  6. void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) {
  7. // perform assertions against person
  8. }
  1. public class PersonAggregator implements ArgumentsAggregator {
  2. @Override
  3. public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) {
  4. return new Person(arguments.getString(0),
  5. arguments.getString(1),
  6. arguments.get(2, Gender.class),
  7. arguments.get(3, LocalDate.class));
  8. }
  9. }

If you find yourself repeatedly declaring @AggregateWith(MyTypeAggregator.class) for multiple parameterized test 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.

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

2.15.6. Customizing Display Names

By default, the display name of a parameterized test invocation contains the invocation index and the String representation of all arguments for that specific invocation. Each of them is preceded by the parameter name (unless the argument is only available via an ArgumentsAccessor or ArgumentAggregator), if present in the bytecode (for Java, test code must be compiled with the -parameters compiler flag).

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

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

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

  1. Display name of container
  2. ├─ 1 ==> the rank of 'apple' is 1
  3. ├─ 2 ==> the rank of 'banana' is 2
  4. └─ 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.

PlaceholderDescription

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

{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.

2.15.7. Lifecycle and Interoperability

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 argument 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.

  1. @BeforeEach
  2. void beforeEach(TestInfo testInfo) {
  3. // ...
  4. }
  5. @ParameterizedTest
  6. @ValueSource(strings = "apple")
  7. void testWithRegularParameterResolver(String argument, TestReporter testReporter) {
  8. testReporter.publishEntry("argument", argument);
  9. }
  10. @AfterEach
  11. void afterEach(TestInfo testInfo) {
  12. // ...
  13. }