Efficient Testing with JUnit5: Parameterized Tests
Welcome to the guide on JUnit Parameterized tests. Here, I’ll show you how to use JUnit to test different data easily. We’ll go through step-by-step examples. Let’s learn how to make testing simpler and more effective!
Prerequisites
- Java: Oracle JDK 21
- JUnit: 5.8.1
- IntelliJ IDEA
Initial Configuration
See my article to initialize your project.
Add Parameterized Test Dependency
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
Getting Started with Parameterized Tests
The @ParameterizedTest
annotation is key in JUnit for defining a parameterized test. Unlike a standard test case, it indicates that the method will run multiple times with various inputs.
Simple Examples
Testing with Null Values (NullSource)
The @NullSource
annotation is used to provide a null
value to the test. The nullSourceTest
method checks if the input string is null
.
@ParameterizedTest
@NullSource
void nullSourceTest(String str) {
assertNull(str);
}
Testing with Empty Source
The @EmptySource
annotation is used for testing with empty strings, lists, sets, and maps.
@ParameterizedTest
@EmptySource
void emptyStringTest(String str) {
assertTrue(str.trim().isEmpty());
}
@ParameterizedTest
@EmptySource
void emptyListTest(List<?> list) {
assertTrue(list.isEmpty());
}
@ParameterizedTest
@EmptySource
void emptySetTest(Set<?> set) {
assertTrue(set.isEmpty());
}
@ParameterizedTest
@EmptySource
void emptyMapTest(Map<?, ?> map) {
assertTrue(map.isEmpty());
}
Testing with Null and Empty Strings
@NullAndEmptySource
combines @NullSource
and @EmptySource
. The nullAndEmptySourceTest
method tests if the string is either null
or empty
@ParameterizedTest
@NullAndEmptySource
void nullAndEmptySourceTest(String str) {
assertTrue(str == null || str.trim().isEmpty());
}
Using Different Arguments in Tests
useful for tests requiring custom or programmatically generated data sets.
Implementing Custom Arguments Providers
By implementing the ArgumentsProvider
interface, this class offers a tailored method to deliver test arguments. This custom provider is utilized in the argumentSourceTest
method through @ArgumentsSource
annotation.
static class NonBlankRandomStringArgumentProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(
Arguments.of("argumentProvider-a"),
Arguments.of("argumentProvider-b"),
Arguments.of("argumentProvider-c")
);
}
}
@ParameterizedTest
@ArgumentsSource(NonBlankRandomStringArgumentProvider.class)
void argumentSourceTest(String str) {
assertFalse(isBlank(str));
}
Custom Annotation for Argument Source
To further streamline the process, a custom annotation @NonBlankRandomStringSource
is created. This annotation encapsulates the @ArgumentsSource
with NonBlankRandomStringArgumentProvider
.
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ArgumentsSource(NonBlankRandomStringArgumentProvider.class)
@interface NonBlankRandomStringSource {
}
@ParameterizedTest
@NonBlankRandomStringSource
void customAnnotationSourceTest(String str) {
assertFalse(isBlank(str));
}
Using MethodSource
In the MethodSourceTest
class, we demonstrate how the @MethodSource
annotation in JUnit facilitates dynamic and complex parameterized tests. We can effectively tailor test parameters by defining a method nonBlankStringsProvider
that returns a stream of specific arguments, such as non-blank strings.
class MethodSourceTest {
private static Stream<Arguments> nonBlankStringsProvider() {
return Stream.of(
Arguments.of("method-a"),
Arguments.of("method-b"),
Arguments.of("method-c")
);
}
@ParameterizedTest
@MethodSource("nonBlankStringsProvider")
void methodSourceTest(String str) {
assertFalse(str == null || str.trim().isEmpty());
}
}
When you run your tests, you’ll notice output indicating that your test method has been executed three times, each time with different arguments provided by the nonBlankStringsProvider
method.
Testing with CSV Files
This section delves into utilizing the @CsvSource
annotation in JUnit, showcasing how to conduct parameterized tests with CSV data efficiently. The CsvSourceTest
class demonstrates two key approaches: direct CSV data use and custom conversion of CSV data.
Direct CSV Data Usage
The csvSourceTest
the method illustrates a straightforward application of @CsvSource
. CSV strings representing dates and their corresponding expected year values are parsed and tested. This @JavaTimeConversionPattern
is used to convert the date strings into a date object.
@ParameterizedTest(name = "getYear({0}) should return {1}")
@CsvSource(value = {"2012/12/25:2012", "2013/12/25:2013", "2014/12/25:2014"}, delimiter = ':')
void csvSourceTest(@JavaTimeConversionPattern("yyyy/MM/dd") LocalDate date, int expected) {
assertEquals(expected, date.getYear());
}
Custom Converter for CSV Data
For more complex scenarios, a custom converter LocalDateConverter
can be used. This converter transforms CSV string data into LocalDate
objects using a specific format. The csvSourceTestWithCustomConverter
method demonstrates this approach, highlighting the flexibility of @CsvSource
handling diverse data types and formats.
static class LocalDateConverter implements ArgumentConverter {
@Override
public Object convert(Object source, ParameterContext context) throws ArgumentConversionException {
if (source instanceof String dateString) {
return LocalDate.parse(dateString, DateTimeFormatter.ofPattern("yyyy/MM/dd"));
}
throw new ArgumentConversionException(source + " is not a string");
}
}
@ParameterizedTest(name = "getYear({0}) should return {1}")
@CsvSource(value = {"2012/12/25:2012", "2013/12/25:2013", "2014/12/25:2014"}, delimiter = ':')
void csvSourceTestWithCustomConverter(@ConvertWith(LocalDateConverter.class) LocalDate date, int expected) {
assertEquals(expected, date.getYear());
}
Testing with Enum Values
The EnumSourceTest
the class provides a comprehensive demonstration of using the @EnumSource
annotation in JUnit for parameterized tests based on enum values.
The enumSourceTest
method exemplifies the basic usage of @EnumSource
. It automatically provides all constants from the Month
enum as parameters. The test ensures that each month's numerical value is between 1 and 12.
@ParameterizedTest(name = "{0}.getValue() must be between 1 and 12")
@EnumSource(Month.class)
void enumSourceTest(Month month) {
final var monthNumber = month.getValue();
assertAll(
() -> assertTrue(monthNumber >= 1),
() -> assertTrue(monthNumber <= 12));
}
Including Certain Enum Constants
In enumSourceOnlyIncludedTest
, the @EnumSource
is configured to include only specific months. This test checks that the included months (April, June, September, and November) all have 30 days.
@ParameterizedTest(name = "{0} must have 30 days")
@EnumSource(value = Month.class, names = {"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"})
void enumSourceOnlyIncludedTest(Month month) {
assertEquals(30, month.length(false));
}
Excluding Certain Enum Constants
Conversely, enumSourceWithoutExcludedTest
uses the @EnumSource
to exclude specific months. It verifies that the remaining months, excluding April, June, September, November, and February, have 31 days.
@ParameterizedTest(name = "{0} must have 31 days")
@EnumSource(
value = Month.class,
names = {"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER", "FEBRUARY"},
mode = EnumSource.Mode.EXCLUDE
)
void enumSourceWithoutExcludedTest(Month month) {
assertEquals(31, month.length(false));
}
Matching Enum Constants with Regular Expressions
Finally, enumSourceOnlyMatchingNameTest
demonstrates the use of regular expressions in @EnumSource
. It selects months whose names end with "BER" and tests whether their names match this pattern.
@ParameterizedTest(name = "{0} must ends with BER")
@EnumSource(
value = Month.class,
names = ".+BER",
mode = EnumSource.Mode.MATCH_ANY
)
void enumSourceOnlyMatchingNameTest(Month month) {
assertTrue(month.name().endsWith("BER"));
}