Introduction
In this example we will create Junit 5 parameterized tests. In our previous example we have seen how to configure Junit 5 for Java projects. In parameterized tests it is possible to run a test multiple times with different arguments. The @ParameterizedTest
annotation is used in the regular @Test
methods to indicate parameterized tests. In addition, at least one source must be declared to provide the arguments for each invocation in the test method.
We will see different sources of data for invoking the tests multiple times with different parameters. The sources can be @ValueSource
, @MethodSource
, @EnumSource
, @ArgumentSource
, @EmptySource
, @NullSource
, @EmptySource
, @CsvSource
.
Prerequisites
Eclipse 4.12, At least Java 8, Junit 5
Value Source
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 palindromesValueSource(String candidate) {
assertTrue(PalindromChecker.isPalindrome(candidate));
}
When executing the above parameterized test method, each invocation will be reported separately.
The isPalindrome()
method is given below:
public static boolean isPalindrome(final String str) {
StringBuilder sb = new StringBuilder(str);
return str.equals(sb.reverse().toString());
}
The following types of literal values are supported by @ValueSource
.
short, byte, int, long, float, double, char, boolean, java.lang.String, java.lang.Class
Method Source
@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. In addition, such factory methods must not accept any arguments.
@ParameterizedTest
@MethodSource("values")
void palindromesMethodSource(String candidate) {
assertTrue(PalindromChecker.isPalindrome(candidate));
}
Notice the @MethodSource
annotation expects the method values()
that provides input data to the test method palindromesMethodSource()
and the parameterized test method accepts single argument, so single value will be passed at a time to the test method.
Here is the method values()
:
public static String[] values() {
return new String[] { "racecar", "radar", "able was I ere I saw elba", "madam" };
}
You can also define the values()
method as follows using Stream of instances of parameter types.
public static Stream<String> values() {
return Stream.of("racecar", "radar", "able was I ere I saw elba", "madam");
}
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.
For test method having two arguments can be written as follows:
public static Object[] strings() {
return new Object[][] { //
{ "str", true }, //
{ "str", true }, //
{ "str", false }, //
{ "exactly 5 objects", false }, //
{ "at least 5 objects", false }, //
{ "\"more than\" 5 objects", false },//
};
}
@ParameterizedTest
@MethodSource("strings")
void stringsMethodSource(String str, boolean trueFalse) {
assertEquals(trueFalse, StringChecker.check(str, trueFalse));
}
The check()
method is defined as below:
public static boolean check(String str, boolean trueFalse) {
return str.equals("str") && trueFalse;
}
An external, static factory method can be referenced by providing its fully qualified method name as demonstrated in the following example.
Let’s say we have defined the following method in class PalindromChecker class:
public static Stream<String> values() {
return Stream.of("racecar", "radar", "able was I ere I saw elba", "madam");
}
Then we can access the above method in the following way:
@ParameterizedTest
@MethodSource("com.roytuts.junit.parameterized.PalindromChecker#values")
void palindromesMethodSourceExternal(String candidate) {
assertTrue(PalindromChecker.isPalindrome(candidate));
}
Null and Empty Sources
To verify the proper behavior of our software we need to check for bad inputs, such as, null and empty values.
@NullSource
provides a single null argument to the annotated @ParameterizedTest
method and cannot be used for a parameter that has a primitive type.
@EmptySource
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
is a composed annotation that combines the functionality of @NullSource
and @EmptySource
.
The following examples shows how to apply the above annotations:
@ParameterizedTest
@NullSource
@ValueSource(strings = { " ", "", "\t", "\n" })
void stringNullSource(String str) {
assertTrue(str == null || str.trim().isEmpty());
}
@ParameterizedTest
@EmptySource
@ValueSource(strings = { " ", "", "\t", "\n" })
void stringEmptySource(String str) {
assertTrue(str == null || str.trim().isEmpty());
}
@ParameterizedTest
// @NullSource
// @EmptySource
@NullAndEmptySource
@ValueSource(strings = { " ", "", "\t", "\n" })
void stringNullAndEmptySource(String str) {
assertTrue(str == null || str.trim().isEmpty());
}
@NullAndEmptySource
can be replaced by putting together @NullSource
and @EmptySource
annotations.
Enum Source
@EnumSource
provides a convenient way to use Enum constants.
The annotation’s value attribute is optional. When omitted, the declared type of the first method parameter is used.
The annotation provides an optional names attribute that lets you specify which constants shall be used as shown in the following example. If omitted, all constants will be used.
The @EnumSource
annotation also provides an optional mode attribute that enables fine-grained control over which constants are passed to the test method.
@ParameterizedTest
@EnumSource(names = { "DAYS", "HOURS" }, value = ChronoUnit.class)
void enumSource(ChronoUnit unit) {
assertTrue(EnumSet.of(ChronoUnit.DAYS, ChronoUnit.HOURS).contains(unit));
}
@ParameterizedTest
@EnumSource(mode = Mode.EXCLUDE, names = { "ERAS", "FOREVER" }, value = ChronoUnit.class)
void enumSourceExclude(ChronoUnit unit) {
assertFalse(EnumSet.of(ChronoUnit.ERAS, ChronoUnit.FOREVER).contains(unit));
}
@ParameterizedTest
@EnumSource(mode = Mode.MATCH_ALL, names = "^.*DAYS$", value = ChronoUnit.class)
void enumSourceRegex(ChronoUnit unit) {
assertTrue(unit.name().endsWith("DAYS"));
}
Csv Source
@CsvSource
allows you to express argument lists as comma-separated values (i.e., String
literals).
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.
@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());
}
Here is the class Person:
package com.roytuts.junit.parameterized;
import java.time.LocalDate;
public class Person {
private String firstName;
private String lastName;
private Gender gender;
private LocalDate dateOfBirth;
public Person(String firstName, String lastName, Gender gender, LocalDate dateOfBirth) {
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.dateOfBirth = dateOfBirth;
}
//getters and setters
}
Gender enum is given below:
package com.roytuts.junit.parameterized;
public enum Gender {
M, F
}
Argument Source
@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(CustomArgumentsProvider.class)
void argumentsSource(String argument) {
assertNotNull(argument);
}
Testing the Tests
Running the above class will run all the test cases.
Source Code
Thanks for reading.