Introduction
In this chapter, we will explore JUnit parameterized tests. Parameterized tests allow you to run the same test with different inputs. This is useful for testing a method with a variety of data sets without having to write multiple test methods.
What are Parameterized Tests?
Parameterized tests are a way to execute the same test method with different parameters. This is especially useful for testing methods that should behave consistently across a range of inputs. JUnit 5 provides excellent support for parameterized tests through the @ParameterizedTest
annotation and a variety of sources for parameters.
Overview of Parameterized Test Annotations
JUnit 5 provides several annotations and methods to create parameterized tests. Here is an overview of the main annotations:
@ParameterizedTest
The @ParameterizedTest
annotation is used to mark a method as a parameterized test. This annotation tells JUnit to run the test method multiple times with different parameters.
@ValueSource
The @ValueSource
annotation provides a simple source of literal values. It can be used to specify arrays of ints, longs, doubles, strings, and other primitive types. This is useful for testing a method with a fixed set of simple values.
Example:
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void testWithIntValueSource(int argument) {
assertTrue(argument > 0);
}
@CsvSource
The @CsvSource
annotation provides a source of comma-separated values. It is useful for testing methods with multiple parameters. Each line in the @CsvSource
represents a set of parameters to be passed to the test method.
Example:
@ParameterizedTest
@CsvSource({
"1, 1, 2",
"2, 3, 5",
"3, 5, 8"
})
void testWithCsvSource(int a, int b, int expectedSum) {
assertEquals(expectedSum, a + b);
}
@MethodSource
The @MethodSource
annotation provides a source of arguments from a static method in the same class or another class. The method must return a stream, iterable, iterator, or array of arguments.
Example:
@ParameterizedTest
@MethodSource("provideStringsForIsBlank")
void testWithMethodSource(String input, boolean expected) {
assertEquals(expected, isBlank(input));
}
private static Stream<Arguments> provideStringsForIsBlank() {
return Stream.of(
Arguments.of(null, true),
Arguments.of("", true),
Arguments.of(" ", true),
Arguments.of("not blank", false)
);
}
@EnumSource
The @EnumSource
annotation provides a source of enum constants. It can be used to test methods with different enum values.
Example:
@ParameterizedTest
@EnumSource(TimeUnit.class)
void testWithEnumSource(TimeUnit timeUnit) {
assertNotNull(timeUnit);
}
@ArgumentsSource
The @ArgumentsSource
annotation allows for custom argument sources by implementing the ArgumentsProvider
interface. This provides maximum flexibility in defining argument sources.
Example:
@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void testWithArgumentsSource(String argument) {
assertNotNull(argument);
}
static class MyArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of("one", "two", "three").map(Arguments::of);
}
}
Example: Creating Parameterized Tests
Maven Dependencies
To use parameterized tests in JUnit 5, you need to include the following dependencies in your pom.xml
file:
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.3</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.10.3</version>
<scope>test</scope>
</dependency>
</dependencies>
Explanation of Dependencies
- JUnit Jupiter API (
junit-jupiter-api
): This dependency provides the JUnit 5 annotations and assertions. - JUnit Jupiter Engine (
junit-jupiter-engine
): This dependency is the runtime engine that executes JUnit 5 tests. - JUnit Jupiter Params (
junit-jupiter-params
): This dependency provides support for parameterized tests in JUnit 5.
Step 1: Create Class Under Test
Let’s create parameterized tests for a simple utility class called MathUtils
.
MathUtils Class
The MathUtils
class will have methods for checking if a number is even and for calculating the factorial of a number.
public class MathUtils {
public boolean isEven(int number) {
return number % 2 == 0;
}
public long factorial(int number) {
if (number < 0) throw new IllegalArgumentException("Number must be non-negative");
long result = 1;
for (int i = 1; i <= number; i++) {
result *= i;
}
return result;
}
}
Step 2: Create a Parameterized Test Class
MathUtilsTest
The MathUtilsTest
class will contain parameterized tests for the MathUtils
class.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*;
import java.util.stream.Stream;
public class MathUtilsTest {
private MathUtils mathUtils = new MathUtils();
@ParameterizedTest
@ValueSource(ints = {2, 4, 6, 8, 10})
public void testIsEvenWithEvenNumbers(int number) {
assertTrue(mathUtils.isEven(number), number + " should be even");
}
@ParameterizedTest
@ValueSource(ints = {1, 3, 5, 7, 9})
public void testIsEvenWithOddNumbers(int number) {
assertFalse(mathUtils.isEven(number), number + " should be odd");
}
@ParameterizedTest
@CsvSource({"0, 1", "1, 1", "2, 2", "3, 6", "4, 24", "5, 120"})
public void testFactorialWithCsvSource(int input, long expected) {
assertEquals(expected, mathUtils.factorial(input), "Factorial of " + input + " should be " + expected);
}
@ParameterizedTest
@MethodSource("provideNumbersForIsEven")
public void testIsEvenWithMethodSource(int number, boolean expected) {
assertEquals(expected, mathUtils.isEven(number), number + " should be " + (expected ? "even" : "odd"));
}
private static Stream<org.junit.jupiter.params.provider.Arguments> provideNumbersForIsEven() {
return Stream.of(
org.junit.jupiter.params.provider.Arguments.of(2, true),
org.junit.jupiter.params.provider.Arguments.of(3, false),
org.junit.jupiter.params.provider.Arguments.of(4, true),
org.junit.jupiter.params.provider.Arguments.of(5, false)
);
}
}
Explanation of the Parameterized Test Class
@ParameterizedTest
: This annotation marks the method as a parameterized test.@ValueSource
: This annotation provides a simple source of literal values. It can be used to specify arrays of ints, strings, etc.@CsvSource
: This annotation provides a source of comma-separated values. It can be used to specify multiple sets of parameters for the test method.@MethodSource
: This annotation provides a source of arguments from a static method. The method must return a stream of arguments.
Running the Parameterized Tests
To run the parameterized tests, simply run the MathUtilsTest
class as a JUnit test. This will execute the test methods with each set of parameters provided by the @ValueSource
, @CsvSource
, and @MethodSource
annotations.
Using IntelliJ IDEA
- Run Parameterized Tests: Click the green run icon next to the
MathUtilsTest
class and selectRun
. - View Results: The results will be displayed in the Run window. A green check mark indicates that all tests passed, while a red cross indicates that some tests failed.
Conclusion
JUnit parameterized tests are a powerful way to test your code with multiple sets of input data. By using annotations like @ParameterizedTest
, @ValueSource
, @CsvSource
, and @MethodSource
, you can create flexible and comprehensive tests that ensure your methods behave correctly across a range of inputs. This approach helps you write more robust and maintainable tests for your Java applications.