Parameterized Tests in JUnit 5

Parameterized Tests in JUnit 5

0 Comments

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.

Test Output of Parametrized Test with Value Source

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.

Output of Null Value

This code uses the @EmptySourceannotation 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.

Output of Empty Source

Parameterized Tests Using @EnumSource

The @EnumSource annotation allows you to pass different values from an enumeration to your test case. The
@EnumSourceannotation 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 @EnumSourceannotation 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.

Output of Enum source

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.

Output to include particular values

You can also specify other criteria with the mode attribute, which supports EXCLUDEINCLUDEMATCH_ALLMATCH_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.

Output for Parametrized Test to exclude some values

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.

Output for CSV source

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.

Output for CSV source

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.

Testing Spring Boot Online Course

About SFG Contributor

Staff writer account for Spring Framework Guru

    You May Also Like

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.