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
1. Null values in @CsvSource
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?
2. Custom nullable argument converter
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.
3. Using custom argument converter
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); }
Conclusion
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.