Parameterized Tests in JUnit 5
0 CommentsLast Updated on May 2, 2021 by jt
Parameterized tests in JUnit 5 enable you to run a test multiple times with different parameters. It helps the developers to save time in writing and executing the tests. We can write JUnit 5 parameterized tests just like regular JUnit 5 tests but have to use the @ParameterizedTest
annotation instead.
In a parameterized test, you can set up a test method that retrieves test data from some data source.
If you are new to writing JUnit Test Cases, I suggest you go through my previous posts on Unit Testing with JUnit
In this post, I will explain how to create parameterized test cases in JUnit 5.
Dependency
To use JUnit 5 parameterized tests, add the following dependencies in your pom.xml
file.
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.7.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.7.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>5.7.0</version> <scope>test</scope> </dependency>
Parameterized Tests with @ValueSource
The @ValueSource
annotation lets you specify an array of literals of primitive types (either short
,byte
,int
,long
,float
,double
,char
,boolean
,String
, or Class
).
To use this annotation, you need to declare a source that will provide the parameters for each invocation of the test case.
This code snippet demonstrates a parameterized test that uses the @ValueSource
annotation to specify String as the source of arguments.
@ParameterizedTest @DisplayName("checks if the given string values ends with the alphabet r") @ValueSource(strings = {"radar","car","door"}) void endsWithTest(String string){ assertTrue(string.endsWith("r")); }
In the code above, @ParameterizedTest
in Line 1 indicates that the annotated method is a parameterized test method.
Line 2 uses the @DisplayName
annotation to display the message present within quotes.
Line 3 passes a different value from the @ValueSource
array to the test method for each test run.
When you run the test, you can see from the output that the test method executed three times with different string values from the source of arguments.
Parameterized Tests with Null or Empty Values
You cannot pass a single null or empty value to a test method through @ValueSource.
From JUnit 5.4 you can use one or a combination of the @NullSource
,@EmptySource
, or @NullAndEmptySource
annotations to pass a single null or empty value to a parameterized test.
The code for passing a null value is this.
@ParameterizedTest @NullSource void test_null(String arg) { assertTrue(arg == null); }
The output of the above code is this.
This code uses the @EmptySource
annotation to pass a single empty argument to the annotated method.
@ParameterizedTest() @EmptySource void test_empty(String arg) { assertTrue(arg.isEmpty()); }
The output of the above code is this.
Parameterized Tests Using @EnumSource
The @EnumSource
annotation allows you to pass different values from an enumeration to your test case. The
@EnumSource
annotation provides an optional names parameter that lets you specify the constants that are to be tested. By default, all the values are tested.
This is an example to assert that all days of the week are from Monday to Sunday.
enum Days { Monday, Tuesday,Wednesday, Thursday, Friday, Saturday, Sunday; }
The code to use the @EnumSource
annotation which takes all the enum values one by one is this.
@ParameterizedTest @EnumSource(Days.class) void test_enum(Days days) { assertNotNull(days); }
On running the test, you should see the test passing successfully.
In order to include only particular values from the enum, you can use the names
attribute.
This is the code.
@ParameterizedTest(name = "#{index} - contains {0}?") @DisplayName("Test to show the constants that are included") @EnumSource(value = Days.class, names = {"Saturday", "Sunday"}) void test_enum_include(Days days) { assertTrue(EnumSet.allOf(Days.class).contains(days)); }
The output is this.
You can also specify other criteria with the mode
attribute, which supports EXCLUDE
, INCLUDE
, MATCH_ALL
, MATCH_ANY
.
The code to exclude values from the enum using the mode
attribute is this.
@ParameterizedTest @DisplayName("Test to show the values by excluding some") @EnumSource(value = Days.class, mode = EXCLUDE, names = {"Saturday", "Sunday"}) void test_enum_exclude(Days days) { assertNotNull(days); }
The output is this.
Parameterized Tests Using @CsvSource
@ValueSource
and @EnumSource
does not allow you to provide multiple parameters to the test method.
With the@CsvSource
annotation, you can pass an array of comma-separated values(CSV) as arguments.
You can also specify the delimiter for multiple arguments in the test method. This annotation is helpful in scenarios where you need to pass an input value and an expected value to the test method at the same time.
Let’s take a string and its length as the parameters and perform the test.
@ParameterizedTest @DisplayName("To check whether the length of the strings are same or not") @CsvSource({ "apple, 5", "orange, 6", }) void test_csv(String string, int length) { assertEquals(length, string.length()); }
Here the @CsvSource
annotation is used to pass two parameters to the test method: a string and a numeric value indicating the length of the string.
On running the test, you will see output similar to this.
Now, let’s use a delimiter ‘|’ in a @CsvSource
annotation and pass three values as its parameters.
@DisplayName("test to calculate the correct sum") @ParameterizedTest(name = "{index} => a={0}, b={1}, sum={2}") @CsvSource( delimiter = '|', value = {"5| 1| 6", "3| 3| 6" }) void sum(int a, int b, int sum) { assertEquals(sum, a + b); }
The output for the above code is this.
Parameterized Tests Using @MethodSource
The @MethodSource
annotation, allows you to define a static method to generate arguments for a parameterized test.
This method returns a stream of arguments of type String
for tests.
private static Stream<Arguments> providedStringsIsPalindrome() { return Stream.of( Arguments.of("word", false), Arguments.of("mom", true), Arguments.of("level", true), Arguments.of("pass", false) ); }
The code of the test method that uses this source is this.
@ParameterizedTest @MethodSource("providedStringsIsPalindrome") void isPlindromeString(String input, boolean expected) { demoParameterized = new DemoParameterized(); assertEquals(expected, demoParameterized.isPalindrome(input)); }
Note that the @MethodSource
argument takes the method name to be used as the source. If no method source is specified, it will look for a method with the same name as that of the test method.
The output on running the test is this.
In the preceding example, the method providing the parameters is in the test class itself.
For reusability across test classes, you can have the method defined in a separate class itself.
The TestParameter
class providing parameters to be reused in tests is this.
TestParameter.java
package com.springframeworkguru.parameterizedtest; import org.junit.jupiter.params.provider.Arguments; import java.util.stream.Stream; public class TestParameter { private static Stream<Arguments> providedStringsIsPalindrome() { return Stream.of( Arguments.of("word", false), Arguments.of("mom", true), Arguments.of("level", true), Arguments.of("pass", false) ); } }
You now need to refactor the @MethodSource
annotation to provide the fully qualified class name along with the method name, like this.
@MethodSource("com.springframeworkguru.parameterizedtest.TestParameter#providedStringsIsPalindrome")
Summary
Parameterized tests are great ways to execute one test as many times as we want, each with a set of different input and output values (parameters), without rewriting our tests.
The best part I find is handling the edge case scenarios efficiently. You can write your first test with a normal set of parameters. Once satisfied, you can start adding the edge cases without code redundancy.
So if you want to write tests with good coverage and that work all dimensions of your code, go for parameterized tests.
You can find the source code of this post on Github.
For in-depth knowledge on Testing in Spring Boot Applications check my Udemy Best Seller Course Testing Spring Boot: Beginner to Guru.