Quick Recap: Parameterized Testing With Multiple Arguments and Expected Values
It has been a while since I’ve written parameterised tests from scratch. Since all those tests live in codebases of previous employers, I don’t have access to them anymore. So, I had to figure out how do you do that again?
Using JUnit 5, setting up a parameterized test is as easy as adding the @ParameterizedTest annotation, specifying an argument source, and consuming said arguments. However, I couldn’t easily find the type of data source specification that worked for my use cases. Most explanations on parameterized testing I found covered either (1) single input values, (2) assertions that didn’t require an explicit expected value (e.g. assertions on whether a result is true or false), or (3) the test method calculating the expected values on the spot.
Where I found them lacking was in explanations or examples providing both input and expected data, especially where either could be of varying lengths (e.g. arrays or a Collection). I wanted to illustrate this based on two use cases, specifically two exercises from Project Euler where using the @MethodSource() annotation provided to be useful.
Use Cases
Fibonacci
Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be: \(1, 2, 35, 8, 13, 21, 34, 55, 89, …\)
By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.
My first step is to write a method that returns the sequence (List<Integer>) when provided a desired length (int). The method under test has the following signature:
import java.util.List;
class Fibonacci {
public List<Integer> sequence(int length) {
// Implementation details omitted.
}
}
Providing the singular int as input is no problem and easily follows from the examples I found online. What tricked me was how to parametrically provide the expected values in the form of a List<Integer>. This is where @MethodSource comes in handy, allowing you to provide a stream of arguments, which can contain basically anything. The parameters for the test method then allow you to reference the data within the method.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.List;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;
class FibonacciTest {
Fibonacci fibonacci = new Fibonacci();
public static Stream<Arguments> provider() {
return Stream.of(
arguments(1, List.of(1)),
arguments(2, List.of(1, 2))
);
}
@ParameterizedTest
@MethodSource("provider")
void testSequenceMethod(int input, List<Integer> expected) {
// given
// when
List<Integer> sequence = fibonacci.sequence(input);
// then
assertThat(sequence).isEqualTo(expected);
}
}
Sum of Squares
The sum of the squares of the first ten natural numbers is \(1^2 + 2^2 + … + 10^2 = 385\).
The square of the sum of the first ten natural numbers is \((1 + 2 + … + 10)^2 = 3025\).
Hence the difference between the sum of the squares of the first ten natural numbers and the square of the sum is \(3025 - 385 = 2640\).
Find the difference between the sum of the squares of the first one hundred natural numbers and the square of the sum.
The method(s) under test this time take an array of ints (int[ ]) and return a single int:
import java.util.Arrays;
class SumOfSquares {
public int calculateSumOfSquares(int... a) {
// Implementation details omitted.
}
public int calculateSquareOfSums(int... a) {
// Implementation details omitted.
}
}
This means we just have to provide arguments containing an int[ ]{...} followed by an int, which can again be put into a stream. Having two methods to test, I want to have two providers of arguments so I created a separate provider method for each test case.
Test class:
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;
class SumOfSquaresTest {
SumOfSquares sumOfSquares = new SumOfSquares();
public static Stream<Arguments> sumOfSquaresProvider() {
return Stream.of(
arguments(new int[]{1}, 1),
arguments(new int[]{1, 2}, 5),
arguments(new int[]{1, 2, 3}, 14)
);
}
public static Stream<Arguments> squareOfSumsProvider() {
return Stream.of(
arguments(new int[]{1}, 1),
arguments(new int[]{1, 2}, 9),
arguments(new int[]{1, 2, 3}, 36)
);
}
@ParameterizedTest
@MethodSource("sumOfSquaresProvider")
void testSumOfSquaresCalculation(int[] input, int expected) {
// given
// when
int result = sumOfSquares.calculateSumOfSquares(input);
// then
assertThat(result).isEqualTo(expected);
}
@ParameterizedTest
@MethodSource("squareOfSumsProvider")
void name(int[] input, int expected) {
// given
// when
int result = sumOfSquares.calculateSquareOfSums(input);
// then
assertThat(result).isEqualTo(expected);
}
}
Conclusions
For these use cases, the most straightforward solution is to use @MethodSource(), with the named method providing a Stream of arguments, which can take pretty much any shape you want. I tried using @CvsSource, but since that primarily works with Strings and String arrays, that requires a few processing steps to convert the strings to the numeric values, which I quickly abandoned.
Addendum
The first page DuckDuckGo contains only blogs and websites containing guides (like Baeldung and geeksforgeeks), with the JUnit User Guide only showing up on the second page1. Looking at Google, the JUnit 5 User Guide does show up as the second result, which would’ve been more helpful to start with. In the end, carefully reading the JUnit user guide gives quite clear answers on how to provide a variety of parameters using the @MethodSource annotation. May this serve as a reminder to myself that official documentation can be a pretty neat resource.
-
For the query “junit 5 parameterized test”. ↩