Efficient Testing with JUnit5: Parameterized Tests

MEHMET ARİF EMRE ŞEN
Yazilim VIP
Published in
5 min readJan 21, 2024

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"));
}

--

--