With JUnit 5 the possibilities to write parameterized tests have changed and improved a lot.
The following short overview covers all new types of possible parameter sources for JUnit 5 tests as well as the new conversion API for test arguments.
In addition we’re showing how parameterized tests were written in JUnit 4.
About
We will be covering all available types of parameter sources in the following sections – all that you need as a prerequisite is Java ™, Maven and a few minutes of your time.
How parameterized tests are written with JUnit 4 is explained in the following article of mine: “New features in JUnit 4.11“.
Dependencies
Since JUnit 5 has chosen a modular approach to structure its functionality, we need to add the following dependencies to our project’s pom.xml (using Maven):
-
junit-jupiter-engine: Public API needed for writing basic tests.
-
junit-jupiter-params: Dependencies to write parameterized tests.
-
junit-platform-launcher: Needed to launch our tests in our IDE of choice like IntelliJ/Eclipse…
In addition, we’re adding the dependency junit-platform-surefire-provider to the Surefire plugin so that we’re able to run all tests with Maven in the command-line.
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.0.0-M5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.0.0-M5</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
Parameter Sources
Here is a list of the available sources that may be used to feed our tests with parameters…
Value Source
A value source allows you to directly specify the parameters as annotation attribute.
Currently you may use the following types here:
-
String values:@ValueSource(strings = \{“foo”, “bar”, “baz”})
-
Double values:@ValueSource(doubles = \{1.5D, 2.2D, 3.0D})
-
Long values:@ValueSource(longs = \{2L, 4L, 8L})
-
Integer values:@ValueSource(ints = \{2, 4, 8})
Here are some concrete tests using value-source:
package com.hascode.tutorial;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class ValueSourcesExampleTest {
@ParameterizedTest
@ValueSource(ints = {2, 4, 8})
void testNumberShouldBeEven(int num) {
assertEquals(0, num % 2);
}
@ParameterizedTest
@ValueSource(strings = {"Radar", "Rotor", "Tenet", "Madam", "Racecar"})
void testStringShouldBePalindrome(String word) {
assertEquals(isPalindrome(word), true);
}
@ParameterizedTest
@ValueSource(doubles = {2.D, 4.D, 8.D})
void testDoubleNumberBeEven(double num) {
assertEquals(0, num % 2);
}
boolean isPalindrome(String word) {
return word.toLowerCase().equals(new StringBuffer(word.toLowerCase()).reverse().toString());
}
}
We may now run these tests like this:
$ mvn test -Dtest=ValueSourcesExampleTest
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.hascode.tutorial.ValueSourcesExampleTest
Tests run: 11, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.087 sec - in com.hascode.tutorial.ValueSourcesExampleTest
Results :
Tests run: 11, Failures: 0, Errors: 0, Skipped: 0
Enum Source
This parameter source allows us to pass in the values of a given enum and additionally restrict which of its values are passed in as parameters using restricting lists or by matching the values to a regular expression.
Let’s take a further look at these concrete examples:
package com.hascode.tutorial;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.EnumSource.Mode;
public class EnumSourcesExampleTest {
@ParameterizedTest(name = "[{index}] TimeUnit: {arguments}")
@EnumSource(TimeUnit.class)
void testTimeUnitMinimumNanos(TimeUnit unit) {
assertTrue(unit.toMillis(2000000L) > 1);
}
@ParameterizedTest
@EnumSource(value = TimeUnit.class, names = {"SECONDS", "MINUTES"})
void testTimeUnitJustSecondsAndMinutes(TimeUnit unit) {
assertTrue(EnumSet.of(TimeUnit.SECONDS, TimeUnit.MINUTES).contains(unit));
assertFalse(EnumSet
.of(TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MILLISECONDS, TimeUnit.NANOSECONDS,
TimeUnit.MICROSECONDS).contains(unit));
}
@ParameterizedTest
@EnumSource(value = TimeUnit.class, mode = Mode.EXCLUDE, names = {"SECONDS", "MINUTES"})
void testTimeUnitExcludingSecondsAndMinutes(TimeUnit unit) {
assertFalse(EnumSet.of(TimeUnit.SECONDS, TimeUnit.MINUTES).contains(unit));
assertTrue(EnumSet
.of(TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MILLISECONDS, TimeUnit.NANOSECONDS,
TimeUnit.MICROSECONDS).contains(unit));
}
@ParameterizedTest
@EnumSource(value = TimeUnit.class, mode = Mode.MATCH_ALL, names = ".*SECONDS")
void testTimeUnitIncludingAllTypesOfSecond(TimeUnit unit) {
assertFalse(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MINUTES).contains(unit));
assertTrue(EnumSet
.of(TimeUnit.SECONDS, TimeUnit.MILLISECONDS, TimeUnit.NANOSECONDS,
TimeUnit.MICROSECONDS).contains(unit));
}
}
Again we may run our tests with Maven in the console:
$ mvn test -Dtest=EnumSourcesExampleTest
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.hascode.tutorial.EnumSourcesExampleTest
Tests run: 18, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.104 sec - in com.hascode.tutorial.EnumSourcesExampleTest
Results :
Tests run: 18, Failures: 0, Errors: 0, Skipped: 0
Method Source
This parameter source allows us to reference a method that provides the input parameters. The referenced method must return either a Stream, an Iterator or an Iterable.
package com.hascode.tutorial;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
public class MethodSourceExampleTest {
@ParameterizedTest
@MethodSource("stringGenerator")
void shouldNotBeNullString(String arg){
assertNotNull(arg);
}
@ParameterizedTest
@MethodSource("intGenerator")
void shouldBeNumberWithinRange(int arg){
assertAll(
() -> assertTrue(arg > 0),
() -> assertTrue(arg <= 10)
);
}
@ParameterizedTest(name = "[{index}] user with id: {0} and name: {1}")
@MethodSource("userGenerator")
void shouldUserWithIdAndName(long id, String name){
assertNotNull(id);
assertNotNull(name);
}
static Stream<String> stringGenerator(){
return Stream.of("hello", "world", "let's", "test");
}
static IntStream intGenerator() {
return IntStream.range(1,10);
}
static Stream<Arguments> userGenerator(){
return Stream.of(Arguments.of(1L, "Sally"), Arguments.of(2L, "Terry"), Arguments.of(3L, "Fred"));
}
}
We’re running our tests in the console with Maven again:
$ mvn test -Dtest=MethodSourceExampleTest
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.hascode.tutorial.MethodSourceExampleTest
Tests run: 16, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.104 sec - in com.hascode.tutorial.MethodSourceExampleTest
Results :
Tests run: 16, Failures: 0, Errors: 0, Skipped: 0
Argument Source
This parameter source allows us to reference a Java class that provides the parameters for the test run.
The providing class must implement the interface ArgumentsProvider, so we need to implement just one method that returns a stream of arguments.
For a better understanding, we’ll have a look at the following example:
package com.hascode.tutorial;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.stream.Stream;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;
public class ArgumentsSourceExampleTest {
@ParameterizedTest
@ArgumentsSource(CustomArgumentsGenerator.class)
void testGeneratedArguments(double number) throws Exception {
assertFalse(number == 0.D);
assertTrue(number > 0);
assertTrue(number < 1);
}
static class CustomArgumentsGenerator implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(Math.random(), Math.random(), Math.random(), Math.random(), Math.random())
.map(Arguments::of);
}
}
}
Running the tests again in the command-line:
$ mvn test -Dtest=ArgumentsSourceExampleTest
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.hascode.tutorial.ArgumentsSourceExampleTest
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.071 sec - in com.hascode.tutorial.ArgumentsSourceExampleTest
Results :
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
CSV Source
This parameter source allows us to specify arguments using strings with comma-separated-values as annotation parameters.
Let’s take a look at a concrete example test:
package com.hascode.tutorial;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
public class CsvSourceExampleTest {
Map<Long, String> idToUsername = new HashMap<>();
{
idToUsername.put(1L, "Selma");
idToUsername.put(2L, "Lisa");
idToUsername.put(3L, "Tim");
}
@ParameterizedTest
@CsvSource({"1,Selma", "2,Lisa", "3,Tim"})
void testUsersFromCsv(long id, String name) {
assertTrue(idToUsername.containsKey(id));
assertTrue(idToUsername.get(id).equals(name));
}
}
Running the test should produce a similar output to this one:
$ mvn test -Dtest=CsvSourceExampleTest
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.hascode.tutorial.CsvSourceExampleTest
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.097 sec - in com.hascode.tutorial.CsvSourceExampleTest
Results :
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
CSV File Source
In addition to the CSV source, we may also specify a file that contains our test arguments as comma-separated-values.
This CSV file named users.csv is used as parameter source for the following test:
1,Selma
2,Lisa
3,Tim
And this is our test reading its arguments from the CSV file:
package com.hascode.tutorial;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;
import org.junit.jupiter.params.provider.CsvSource;
public class CsvFileSourceExampleTest {
Map<Long, String> idToUsername = new HashMap<>();
{
idToUsername.put(1L, "Selma");
idToUsername.put(2L, "Lisa");
idToUsername.put(3L, "Tim");
}
@ParameterizedTest
@CsvFileSource(resources = "/users.csv")
void testUsersFromCsv(long id, String name) {
assertTrue(idToUsername.containsKey(id));
assertTrue(idToUsername.get(id).equals(name));
}
}
Running the test should produce a similar result:
$ mvn test -Dtest=CsvFileSourceExampleTest
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.hascode.tutorial.CsvFileSourceExampleTest
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.102 sec - in com.hascode.tutorial.CsvFileSourceExampleTest
Results :
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
Argument Conversion
JUnit allows us to convert arguments to the target format we need in our tests.
There are two possible conversion types:
Implicit Conversion
JUnit offers multiple built-in type converters, especially to convert between strings and the common value types as this is needed for the CSV sources.
The following target types for a by-string conversion are available (primitives as their wrapper types):
-
Boolean
-
Byte
-
Character
-
Short
-
Integer
-
Long
-
Float
-
Double
-
Enum subclass
-
Instant
-
LocalDate
-
LocalDateTime
-
LocalTime
-
OffsetTime
-
OffsetDateTime
-
Year
-
YearMonth
-
ZonedDateTime
For more detailed information, please refer to the JUnit User Manual here.
Explicit Conversion
If we want to control the conversion or we need to convert to a type that is not in the list above, we may use the annotation @ConvertWith(MyConverter.class) for our argument and let our converter class implement SimpleArgumentConverter.
The following example shows a converter in action that converts between a string and an UUID.
package com.hascode.tutorial;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.LocalDate;
import java.time.Month;
import java.util.UUID;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.converter.ConvertWith;
import org.junit.jupiter.params.converter.SimpleArgumentConverter;
import org.junit.jupiter.params.provider.ValueSource;
public class ArgumentsConversionExampleTest {
@ParameterizedTest
@ValueSource(strings = "2017-07-11")
void testImplicitArgumentConversion(LocalDate date) throws Exception {
assertTrue(date.getYear() == 2017);
assertTrue(date.getMonth().equals(Month.JULY));
assertTrue(date.getDayOfMonth() == 11);
}
@ParameterizedTest
@ValueSource(strings = "B4627B3B-ACC4-44F6-A2EB-FCC94DAB79A5")
void testImplicitArgumentConversion(@ConvertWith(ToUUIDArgumentConverter.class) UUID uuid)
throws Exception {
assertNotNull(uuid);
assertTrue(uuid.getLeastSignificantBits() == -6706989278516512347L);
}
static class ToUUIDArgumentConverter extends SimpleArgumentConverter {
@Override
protected Object convert(Object source, Class<?> targetType) {
assertEquals(UUID.class, targetType, "may only convert to UUID");
return UUID.fromString(String.valueOf(source));
}
}
}
Running our tests should look similar to this output:
$ mvn test -Dtest=ArgumentsConversionExampleTest
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.hascode.tutorial.ArgumentsConversionExampleTest
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.083 sec - in com.hascode.tutorial.ArgumentsConversionExampleTest
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
Tutorial Sources
Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:
git clone https://github.com/hascode/junit5-params-sample.git
Resources
Troubleshooting
-
“java.lang.NoSuchMethodError: org.junit.platform.commons.util.ReflectionUtils.getDefaultClassLoader(.. when running in IDE like IntelliJ” Add the following dependency to your project’s pom.xml:
<dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-launcher</artifactId> <version>1.0.0-M5</version> </dependency>
-
“[ERROR] Java heap space → [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/OutOfMemoryError” There was an issue with the Surefire-plugin, also documented in https://github.com/junit-team/junit5/issues/855 – upgrading the versions should help.
Appendix: Parameterized Tests with JUnit 4
This snippet is taken my blog article “https://www.hascode.com/2012/11/new-features-in-junit-4-11/#Parameterized_Tests[New features in JUnit 4.11]“…
@RunWith(Parameterized.class)
public class ParameterizedTest {
@Parameters(name = "Run #{index}: {0}^2={1}")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 1, 1 }, { 2, 4 }, { 3, 9 },
{ 4, 16 }, { 5, 25 } });
}
private final int input;
private final int resultExpected;
public ParameterizedTest(final int input, final int result) {
this.input = input;
this.resultExpected = result;
}
@Test
public void testUserMapping() {
Calculator calc = new Calculator();
assertEquals(resultExpected, calc.square(input));
}
}