In this post, we’ll learn how to use Parameterized Tests in JUnit 5
· Prerequisites
· Overview
∘ About Junit 5
∘ What are Parameterized Tests?
· Getting Started
∘ Traditional JUnit 5 tests
∘ Write the parameterized tests
· Conclusion
· References
Prerequisites
This is the list of all the prerequisites:
- Maven 3.6.+
- Java 8 or later
Overview
About Junit 5
JUnit is one of the most popular unit-testing frameworks in the Java ecosystem. JUnit 5 is the current generation of the JUnit testing framework, which provides a modern foundation for developer-side testing on the JVM. This includes focusing on Java 8 and above, as well as enabling many different styles of testing. Unlike previous versions of JUnit, JUnit 5 is composed of several different modules from three different sub-projects.

What are Parameterized Tests?
Parameterized tests are a feature that allows us to run a test multiple times with different arguments. They are declared just like regular @Test methods but use the @ParameterizedTest annotation instead. In addition, you must declare at least one source that will provide the arguments for each invocation and then consume the arguments in the test method.
The parameterized tests provide many advantages such as:
- Makes code maintenance easier
- Avoid redundancy and code duplication in test cases
- The ease of testing new test cases by simply adding new inputs.
- Improves readability by structuring the input data.
Getting Started
We will start by start by creating a simple Maven project.
To use parameterized tests we need to add a dependency on the junit-jupiter-params artifact.
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
Let’s assume the following case:
We need to calculate the cost of purchases paid for customers.
- When the total amount of purchases is less than $20, no discount will be applied.
- When the total amount of purchases is greater than $20 and less than or equal to $35, then there will be a 5% reduction to apply.
- When the total amount of purchases is greater than $35 and less than or equal to $50, then there will be a 10% discount to apply.
- When the total discount is greater than $50, then there will be a 15% discount to apply.
public class CostCalculator {
public double getCostToPaid(double total) {
if (total < 20) {
return total;
}
if (total > 20 && total <= 35) {
return total - (total * 5) / 100;
}
if (total > 35 && total <= 50) {
return total - (total * 10) / 100;
}
return total - (total * 15) / 100;
}
}
Traditional JUnit 5 tests
Before diving into Parameterized Tests, let’s start by writing non-parameterized tests.
To test this method with different inputs, we write several unit test cases like this:
class CostCalculatorTest {
@Test
void testTotalCostLessThan20() {
CostCalculator calculator = new CostCalculator();
assertEquals(10, calculator.getCostToPaid(10));
}
@Test
void testTotalCostBetween20_35() {
CostCalculator calculator = new CostCalculator();
assertEquals(23.75, calculator.getCostToPaid(25));
}
@Test
void testTotalCostBetween35_50() {
CostCalculator calculator = new CostCalculator();
assertEquals(37.8, calculator.getCostToPaid(42));
}
@Test
void testTotalCostGreater50() {
CostCalculator calculator = new CostCalculator();
assertEquals(59.5, calculator.getCostToPaid(70));
}
}
Write the parameterized tests
We will now rewrite the traditional tests above using parameterized tests. JUnit Jupiter provides quite a few source annotations for using the parameterized test. Please refer to the Javadoc in the org.junit.jupiter.params.provider package for additional information.
- @CsvSource
This annotation allows us to express argument lists as comma-separated values (i.e. CSV string literals). Each string provided via the value attribute in @CsvSource represents a CSV record and results in a call to the parameterized test.
Here is an example:
class CostCalculatorCsvSourceTest {
@ParameterizedTest
@CsvSource({
"10, 10",
"25, 23.75",
"42, 37.8",
"70, 59.5",
})
void testTotalCost(double input, double expectedResult) {
CostCalculator calculator = new CostCalculator();
assertEquals(expectedResult, calculator.getCostToPaid(input));
}
}
Using a text block, the previous example can be implemented as follows.
class CostCalculatorCsvSourceTest {
@ParameterizedTest
@CsvSource(delimiter = '|', quoteCharacter = '"', textBlock = """
#-----------------------------
# 10 | 10
#-----------------------------
25 | 23.75
#-----------------------------
42 | 37.8
#-----------------------------
70 | 59.5
#-----------------------------
150 | 127.5
#-----------------------------
""")
void testTotalCost2(double input, double expectedResult) {
CostCalculator calculator = new CostCalculator();
assertEquals(expectedResult, calculator.getCostToPaid(input));
}
}
- @CsvFileSource
This annotation lets us use comma-separated value (CSV) files from the classpath or the local file system. Similar to @CsvSource, here also, each CSV record results in one execution of the parameterized test.
class CostCalculatorCsvFileSourceTest {
@ParameterizedTest(name = "{index} ==> TOTAL COST: {0} - COST TO PAID: {1}")
@CsvFileSource(resources = "/cost-column.csv", numLinesToSkip = 1)
void testTotalCost3(double input, double expectedResult) {
CostCalculator calculator = new CostCalculator();
assertEquals(expectedResult, calculator.getCostToPaid(input));
}
}
cost-column.csv
TOTAL, EXPECTED_RESULT
10, 10
25, 23.75
42, 37.8
70, 59.5
The first record may optionally be used to supply CSV headers. It supports various other attributes: numLinesToSkip, useHeadersInDisplayName, lineSeparator, delimiterString, etc.
- @ValueSource
This is one of the simplest sources possible. It allows a single array of literal values to be specified and can only be used to provide a single argument per parameterized test invocation. The literal values supported by @ValueSource are: short, byte, int, long, float, double, char, boolean, String and Class.
class CostCalculatorValueSourceTest {
@ParameterizedTest
@ValueSource(doubles = {10.0, 25.0, 42.0, 70.0})
void testTotalCost(double input) {
Map<Double, Double> expectedResult = new HashMap<>();
expectedResult.put(10.0, 10.0);
expectedResult.put(25.0, 23.75);
expectedResult.put(42.0, 37.8);
expectedResult.put(70.0, 59.5);
CostCalculator calculator = new CostCalculator();
assertEquals(expectedResult.get(input), calculator.getCostToPaid(input));
}
}
- @MethodSource
This annotation allows us to refer to one or more factory methods of the test class or external classes.
class CostCalculatorMethodSourceTest {
@DisplayName("A parameterized test with arguments")
@ParameterizedTest(name = "{index} ==> TOTAL COST: {0} - COST TO PAID: {1}")
@MethodSource("inputsAndResults")
void testTotalCost(double input, double expectedResult) {
CostCalculator calculator = new CostCalculator();
assertEquals(expectedResult, calculator.getCostToPaid(input));
}
static Stream<Arguments> inputsAndResults() {
return Stream.of(
Arguments.of(10, 10),
Arguments.of(25, 23.75),
Arguments.of(42, 37.8),
Arguments.of(70, 59.5),
Arguments.of(150, 127.5)
);
}
}
- @EnumSource
This annotation provides a convenient way to use Enum constants.
class CostCalculatorEnumSourceTest {
@ParameterizedTest
@EnumSource(TestCostPaid.class)
void testTotalCost2(TestCostPaid testCostPaid) {
CostCalculator calculator = new CostCalculator();
assertEquals(testCostPaid.getExpected(), calculator.getCostToPaid(testCostPaid.getInput()));
}
}
enum TestCostPaid {
TEST_10(10, 10),
TEST_25(25, 23.75),
TEST_42(42, 37.8),
TEST_70(70, 59.5);
private final double input;
private final double expected;
TestCostPaid(double input, double expected) {
this.input = input;
this.expected = expected;
}
public double getInput() {
return input;
}
public double getExpected() {
return expected;
}
}
The @EnumSource annotation also provides an optional mode attribute that enables fine-grained control over which constants are passed to the test method.
Conclusion
Well done !!. In this post, we’ve learned how to write JUnit 5 parameterized tests. Parameterized testing is a powerful feature of JUnit 5 that eliminates the need for duplicate test cases and provides the ability to run the same test multiple times with varying inputs.
The complete source code is available on GitHub.
You can reach out to me and follow me on Medium, Twitter, GitHub
Thanks for reading!
References
- https://junit.org/junit5/
- https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/package-summary.html
** Cover image by Lucas Santos on Unsplash