Testing

@ParameterizedTest with null values in @CvsSource

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.

Daniel

Share
Published by
Daniel

Recent Posts

Does Java have default parameters?

Short answer: No.Fortunately, you can simulate them.Many programming languages like C++ or modern JavaScript have…

4 years ago

Understanding JavaScript Promise

The JavaScript Promise is a concept that every modern self-respecting web developer should be familiar…

5 years ago

What is Spring bean?

In short, a Spring bean is an object which Spring framework manages at runtime. A…

5 years ago

Injecting Spring Prototype bean into Singleton bean

Have you ever wonder why singleton is the default scope for Spring beans? Why isn't…

5 years ago

How to bind @RequestParam to object in Spring

Do you have multiple parameters annotated with @RequestParam in a request mapping method and feel…

5 years ago

Activating Spring Boot profile with Maven profile

Some teams prefer having a separate Maven build profile for each application runtime environment, like…

5 years ago