Writing Parameterized Tests With JUnit 5
After we have finished this webinar, we
- Know how we can get the required dependencies with Maven and Gradle.
- Can write parameterized tests with JUnit 5.
- Know how we can customize the name of each test method invocation.
- Understand how we can use different argument sources.
This webinar assumes that:
Getting the Required Dependencies
Before we can write parameterized tests with JUnit 5, we have to declare the junit-jupiter-params dependency in our build script.
If we are using Maven, we have to add the following snippet to our POM file:
1<dependency>
2 <groupId>org.junit.jupiter</groupId>
3 <artifactId>junit-jupiter-params</artifactId>
4 <version>5.0.1</version>
5 <scope>test</scope>
6</dependency>
If we are using Gradle, we have to add the following snippet to our build.gradle file:
1testCompile(
2 'org.junit.jupiter:junit-jupiter-params:5.0.1'
3)
Let’s move on and write our first parameterized tests with JUnit 5.
Writing Our First Parameterized Tests
We can write a simple parameterized test with JUnit 5 by following these steps:
- Add a new test method to our test class and specify the method parameters that should be passed to our test method.
- Annotate the test method with the
@DisplayNameannotation and specify the display name. - Annotate the test method with the
@ParameterizedTestannotation. This annotation identifies parameterized test methods. - Provide the method parameters that are passed to our test method. Because our test method takes one
Stringobject as a method parameter, we can provide the method parameters by annotating our test method with the@ValueSourceannotation.
After we have written our first parameterized test, the source code of our test class looks as follows:
1import org.junit.jupiter.api.DisplayName;
2import org.junit.jupiter.params.provider.ValueSource;
3
4import static org.junit.jupiter.api.Assertions.assertNotNull;
5
6@DisplayName("@ValueSource example")
7class ValueSourceExampleTest {
8
9 @DisplayName("Use the parameters provided by the @ValueSource annotation")
10 @ParameterizedTest
11 @ValueSource(strings = {"Hello", "World"})
12 void shouldPassNotNullMessageAsMethodParameter(String message) {
13 assertNotNull(message);
14 }
15}
The @ValueSource annotation allows us to specify the method parameters by using an array that can contain primitive types. Also, we can use this annotation only if our test method takes only one method parameter.
Additional Reading:
When we run our parameterized test, we should see an output that looks as follows:
1@ValueSource example
2|_ Use the parameters provided by the @ValueSource annotation
3 |_ [1] Hello
4 |_ [2] World
Even though this output looks quite clean, sometimes we want to provide our own display name for each method invocation. Let’s find out how we can do it.
Customizing the Display Name of Each Method Invocation
We can customize the display name of each method invocation by setting the value of the @ParameterizedTest annotation’s name attribute. This attribute supports the following placeholders:
{index}: The index of the current invocation. Note that the index of the first invocation is one.{arguments}: A comma separated list that contains all method parameters.{i}: The index of the actual method parameter. Note that the index of the first method parameter is zero.
Let’s provide a custom display name to our test method. This display name must display the index of the current invocation and the used method parameter. After we have customized the display name, the source code of our test class looks as follows:
1import org.junit.jupiter.api.DisplayName;
2import org.junit.jupiter.params.ParameterizedTest;
3import org.junit.jupiter.params.provider.ValueSource;
4
5import static org.junit.jupiter.api.Assertions.assertNotNull;
6
7@DisplayName("@ValueSource example")
8class ValueSourceExampleTest {
9
10 @DisplayName("Use the parameters provided by the @ValueSource annotation")
11 @ParameterizedTest(name = "{index} => message=''{0}''")
12 @ValueSource(strings = {"Hello", "World"})
13 void shouldPassNotNullMessageAsMethodParameter(String message) {
14 assertNotNull(message);
15 }
16}
When we run our parameterized test, we should see an output that looks as follows:
1@ValueSource example
2|_ Use the parameters provided by the @ValueSource annotation
3 |_ 1 => message='Hello'
4 |_ 2 => message='World'
Additional Reading:
Next, we will find out how we can use different argument sources.
Using Argument Sources
We can already provide the required method parameters by using the @ValueSource annotation. However, JUnit 5 provides support for other argument sources as well. Let’s start by taking a look at the @EnumSource annotation.
Using the @EnumSource Annotation
The @EnumSource annotation allows us to specify the enum values that are passed to our parameterized test. Let’s assume that we have to write a parameterized test that takes the values of the Pet enum as method parameters. The source code of the Pet enum looks as follows:
1enum Pet {
2 CAT,
3 DOG;
4}
If we want to pass all enum values to our test method, we have to annotate our test method with the @EnumSource annotation and specify the enum whose values are passed to our test method. After we have done this, the source code of our test class looks as follows:
1import org.junit.jupiter.api.DisplayName;
2import org.junit.jupiter.params.ParameterizedTest;
3import org.junit.jupiter.params.provider.EnumSource;
4
5import static org.junit.jupiter.api.Assertions.assertNotNull;
6
7@DisplayName("@EnumSource example")
8class EnumSourceExampleTest {
9
10 @DisplayName("Use all enum values of the Pet enum")
11 @ParameterizedTest(name = "{index} => pet=''{0}''")
12 @EnumSource(Pet.class)
13 void shouldPassNotNullEnumValuesAsMethodParameter(Pet pet) {
14 assertNotNull(pet);
15 }
16}
If we want to specify the enum values that are passed to our test method, we have to specify the enum values by setting the value of the @EnumSource annotation’s names attribute. Let’s ensure that the value: Pet.CAT is passed to our test method.
After we have specified the used enum value, the source code of our test class looks as follows:
1import org.junit.jupiter.api.DisplayName;
2import org.junit.jupiter.params.ParameterizedTest;
3import org.junit.jupiter.params.provider.EnumSource;
4
5import static org.junit.jupiter.api.Assertions.assertNotNull;
6
7@DisplayName("@EnumSource example")
8class EnumSourceExampleTest {
9
10 @DisplayName("Use the specified enum value of the Pet enum")
11 @ParameterizedTest(name = "{index} => pet=''{0}''")
12 @EnumSource(value = Pet.class, names = {"CAT"})
13 void shouldPassNotNullEnumValueAsMethodParameter(Pet pet) {
14 assertNotNull(pet);
15 }
16}
The @EnumSource annotation provides quite versatile support for filtering enum values. If you want to get more information about this, you should take a look at the following resources:
Additional Reading:
Let’s move on and find out how we can pass multiple method parameters to our test method.
Using the @CsvSource Annotation
If our test data is used by only one parameterized test, we can configure our test data by using the @CsvSource annotation. When we add this annotation to a test method, we have to configure the test data by using an array of String objects. Each string contains a comma separated list that defines the method parameters of our test method.
Let’s configure the method parameters that are passed to the sum() method. After we have done this, the source code of our test class looks as follows:
1import org.junit.jupiter.api.DisplayName;
2import org.junit.jupiter.params.ParameterizedTest;
3import org.junit.jupiter.params.provider.CsvSource;
4
5import static org.junit.jupiter.api.Assertions.assertEquals;
6
7@DisplayName("@CsvSource example")
8class CsvSourceExampleTest {
9
10 @DisplayName("Use the parameters provided by the @CsvSource annotation")
11 @ParameterizedTest(name = "{index} => a={0}, b={1}, sum={2}")
12 @CsvSource({
13 "1, 1, 2",
14 "2, 3, 5"
15 })
16 void sum(int a, int b, int sum) {
17 assertEquals(sum, a + b);
18 }
19}
Additional Reading:
Even though this looks quite clean, sometimes we have so much test data that it doesn’t make sense to add it to our test class because our test class would become unreadable. Let’s find out how we can load our test data from a CSV file.
Using the @CsvFileSource Annotation
We can load our test data from a CSV file by following these steps:
First, we have to create a CSV file that contains our test data and put this file to the classpath. When we add our test data to the created CSV file, we have to follow these rules:
- One line must contain all method parameters of one invocation.
- The values found from each line must use the same order as the method parameters of our test method.
The test-data.csv file configures the test data that is passed to our this method. This file is found from the src/test/resources directory, and its content looks as follows:
11,1,2
22,3,5
33,5,8
Second, we have to annotate our test method with the @CsvFileSource annotation and configure the location of our CSV file. After we have done this, the source code of our test class looks as follows:
1import org.junit.jupiter.api.DisplayName;
2import org.junit.jupiter.params.ParameterizedTest;
3import org.junit.jupiter.params.provider.CsvFileSource;
4import org.junit.jupiter.params.provider.CsvSource;
5
6import static org.junit.jupiter.api.Assertions.assertEquals;
7
8@DisplayName("@CsvFileSource example")
9class CsvFileSourceExampleTest {
10
11 @DisplayName("Use the parameters provided by the test-data.csv file")
12 @ParameterizedTest(name = "{index} => a={0}, b={1}, sum={2}")
13 @CsvFileSource(resources = "/test-data.csv")
14 void sum(int a, int b, int sum) {
15 assertEquals(sum, a + b);
16 }
17}
Additional Reading:
Next, we will find out how we can provide our test data by using a factory method.
Using the @MethodSource Annotation
If we we want to provide our test data by using a factory method, we have to follow these steps:
- Add a
staticmethod to our test class. This method returns aStreamofArgumentsobjects and doesn’t take any method parameters. - Implement this method by returning our test data.
- Annotate our test method with the
@MethodSourceannotation and configure the name of the argument source method.
After we have implemented our argument source method, the source code of our test class looks as follows:
1import org.junit.jupiter.api.DisplayName;
2import org.junit.jupiter.params.ParameterizedTest;
3import org.junit.jupiter.params.provider.Arguments;
4import org.junit.jupiter.params.provider.MethodSource;
5
6import java.util.stream.Stream;
7
8import static org.junit.jupiter.api.Assertions.assertEquals;
9
10@DisplayName("@MethodSource example")
11class MethodSourceExampleTest {
12
13 @DisplayName("Use the parameters provided by the sumProvider() method")
14 @ParameterizedTest(name = "{index} => a={0}, b={1}, sum={2}")
15 @MethodSource("sumProvider")
16 void sum(int a, int b, int sum) {
17 assertEquals(sum, a + b);
18 }
19
20 private static Stream<Arguments> sumProvider() {
21 return Stream.of(
22 Arguments.of(1, 1, 2),
23 Arguments.of(2, 3, 5)
24 );
25 }
26}
There are five things I want to point out:
- The argument source method must return a
Stream,Iterable,Iterator, or an array of arguments. - The argument source method can also return streams that contain primitive types.
- The returned arguments must use the same order as the method parameters of our test method.
- The argument source method must not take any method parameters.
- If we use the default lifecycle configuration, the argument source method must be
static.
Additional Reading:
Let’s find out, how we can implement a custom ArgumentsProvider.
Using the @ArgumentSource Annotation
If we need extra flexibility, we can use a custom ArgumentsProvider by following these steps:
First, we have write a custom ArgumentsProvider class by following these steps:
- Create a class that implements the
ArgumentsProviderinterface. - Implement the
provideArguments()method by returning our test data. Remember that the returned test data must use the same order as the method parameters of our test method.
After we have created a custom ArgumentsProvider class, the source code of our test class looks as follows:
1import org.junit.jupiter.api.DisplayName;
2import org.junit.jupiter.api.extension.ExtensionContext;
3import org.junit.jupiter.params.ParameterizedTest;
4import org.junit.jupiter.params.provider.Arguments;
5import org.junit.jupiter.params.provider.ArgumentsProvider;
6
7import java.util.stream.Stream;
8
9import static org.junit.jupiter.api.Assertions.assertEquals;
10
11@DisplayName("@ArgumentsSource example")
12class ArgumentsSourceExampleTest {
13
14 @DisplayName("Use the parameters provided by the CustomArgumentProvider")
15 @ParameterizedTest(name = "{index} => a={0}, b={1}, sum={2}")
16 void sum(int a, int b, int sum) {
17 assertEquals(sum, a + b);
18 }
19
20 static class CustomArgumentProvider implements ArgumentsProvider {
21
22 @Override
23 public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {
24 return Stream.of(
25 Arguments.of(1, 1, 2),
26 Arguments.of(2, 3, 5)
27 );
28 }
29 }
30}
Second, we have to configure the used ArgumentsProvider by annotating our test method with the @ArgumentsSource annotation. After we have done this, the source code of test class looks as follows:
1import org.junit.jupiter.api.DisplayName;
2import org.junit.jupiter.api.extension.ExtensionContext;
3import org.junit.jupiter.params.ParameterizedTest;
4import org.junit.jupiter.params.provider.Arguments;
5import org.junit.jupiter.params.provider.ArgumentsProvider;
6import org.junit.jupiter.params.provider.ArgumentsSource;
7
8import java.util.stream.Stream;
9
10import static org.junit.jupiter.api.Assertions.assertEquals;
11
12@DisplayName("@ArgumentsSource example")
13class ArgumentsSourceExampleTest {
14
15 @DisplayName("Use the parameters provided by the CustomArgumentProvider")
16 @ParameterizedTest(name = "{index} => a={0}, b={1}, sum={2}")
17 @ArgumentsSource(CustomArgumentProvider.class)
18 void sum(int a, int b, int sum) {
19 assertEquals(sum, a + b);
20 }
21
22 static class CustomArgumentProvider implements ArgumentsProvider {
23
24 @Override
25 public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {
26 return Stream.of(
27 Arguments.of(1, 1, 2),
28 Arguments.of(2, 3, 5)
29 );
30 }
31 }
32}
Additional Reading:
We can now get the required dependencies and write parameterized tests with JUnit 5. Let’s summarize what we learned from this webinar.
Summary
This webinar has taught us four things:
- Before we can write parameterized tests with JUnit 5, we have to declare the
junit-jupiter-paramsdependency in our build script. - We have to annotate our parameterized test method with the
@ParameterizedTestannotation. - We can customize the display name of each method invocation by setting the value of the
@ParameterizedTestannotation’snameattribute. - When we configure our test data, our test data must use the same order as the method parameters of our test method.