Writing parameterized tests in JUnit 4 was pretty cumbersome. JUnit 5 introduced several useful improvements to the framework and running the same test with different arguments is much simpler than in the previous version. However, there is one small issue with passing null values in such arguments.
In this post, I’m going to show you how to pass null in @CvsSource and @ValueSource for @ParametrziedTest in JUnit 5.
Advertisement
In order to analyze the problem, we need a sample case.
Let’s say that we create a class called DateRange which contains two boundary dates of a time period. You can create a new object only if you pass at least one boundary date to the constructor. We also need to make sure the start date is before the end date.
With @ParametrizedTest from JUnit 5 we can describe given requirements with two following tests.
@ParameterizedTest @CsvSource({ "2017-06-01, 2018-10-15", "null, 2018-10-15", "2017-06-01, null" }) void shouldCreateValidDateRange(LocalDate startDate, LocalDate endDate) { new DateRange(startDate, endDate); } @ParameterizedTest @CsvSource({ "2018-10-15, 2017-06-01", "null, null" }) void shouldNotCreateInvalidDateRange(LocalDate startDate, LocalDate endDate) { assertThrows(IllegalArgumentException.class, () -> new DateRange(startDate, endDate)); }
However, when you try to execute these tests, you will end up with an error similar to the one presented below.
org.junit.jupiter.api.extension.ParameterResolutionException: Error converting parameter at index 0: Failed to convert String “null” to type java.time.LocalDate
Although JUnit 5 comes with numerous built-in converters from string values to different types, the null value isn’t accepted in @ValueSource or @CsvSource.
So how can you force JUnit 5 to work with null literals?
Fortunately, JUnit 5 is flexible and we can easily extend its features.
By default, the framework uses the DefaultArgumentConverter class to convert Strings into other types. Our goal is to represent String “null” as the null literal. Other string values should be converted with the default converter.
In order to do so, we create a class which extends SimpleArgumentConverter and implement its abstract convert() method. In the body, we check for “null” values. In other cases, we execute the default converter.
import org.junit.jupiter.params.converter.DefaultArgumentConverter; public final class NullableConverter extends SimpleArgumentConverter { @Override protected Object convert(Object source, Class<?> targetType) throws ArgumentConversionException { if ("null".equals(source)) { return null; } return DefaultArgumentConverter.INSTANCE.convert(source, targetType); } }
Note that the signature of the DefaultArgumentConverter.convert() presented above is available since JUnit 5.2.
Once our custom converter is ready, we can call it in our tests using the @ConvertWith annotation.
@ParameterizedTest @CsvSource({ "2017-06-01, 2018-10-15", "null, 2018-10-15", "2017-06-01, null" }) void shouldCreateValidDateRange(@ConvertWith(NullableConverter.class) LocalDate startDate, @ConvertWith(NullableConverter.class) LocalDate endDate) { new DateRange(startDate, endDate); }
At this point, you should already know how to accept null values in JUnit 5 argument sources for parameterized tests. I hope such conversion will be automatic in future releases of the framework. For now, we need a small workaround.
In case of any question please leave it in the comments. If you want to know about the latest posts, follow me or join the subscription list.
Short answer: No.Fortunately, you can simulate them.Many programming languages like C++ or modern JavaScript have…
The JavaScript Promise is a concept that every modern self-respecting web developer should be familiar…
In short, a Spring bean is an object which Spring framework manages at runtime. A…
Have you ever wonder why singleton is the default scope for Spring beans? Why isn't…
Do you have multiple parameters annotated with @RequestParam in a request mapping method and feel…
Some teams prefer having a separate Maven build profile for each application runtime environment, like…