Introduction
Parameterized tests are a JUnit feature that allows us to execute the same test multiple times with different input data by making use of the annotation @ParameterizedTest
.
When to use Parameterized tests
Let’s say we want to test our brand-new class:
public class Calculator {
public int divide(int num1, int num2) {
return num1 / num2;
}
}
(I know it’s pretty complex logic 😉). A typical scenario for this case is when we divide by zero, we will get an exception. Let’s test that with a simple test class:
class CalculatorTest {
static Calculator calculator;
@BeforeAll
static void setup() {
calculator = new Calculator();
}
@Test
void it_should_throw_exception_when_divided_by_zero() {
assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0));
}
}
So we execute the test and see what happens:
Nice! the test has worked out as expected.
Now, let’s say we want to prove that this won’t happen for several inputs. We could start creating tests for each input, but if we want to test 6 different inputs, that would require 6 different tests.
For this kind of occasion, we can make use of the @ParameterizedTest
annotation.
How to use Parameterized tests
The first thing we need to know in order to make use of Parameterized tests is to be able to pass our custom input data to the test we want to parameterize. There are multiple ways of doing this but let’s focus on the main ones.
So, the question is: How do we pass custom input data to parameterized tests?
@ValueSource
We can use the @ValueSource
annotation
for some simple input data
@ParameterizedTest
@ValueSource(ints = {1, 2, -1, -2})
void it_should_not_throw_exception_with_valid_values(int num) {
assertDoesNotThrow(() -> calculator.divide(1, num));
}
This creates 4 different tests, one for each value in the @ValueSource
annotation.
@MethodSource
What if we want to pass multiple parameters, or use a more complex logic for the creation of the input data? We could use the @MethodSource
annotation. This annotation will receive as a parameter, the name of the method that will return the input data for the test.
For instance, if we want to pass both numbers when dividing we could do something like this:
@ParameterizedTest
@MethodSource("getValidValues")
void it_should_not_throw_exception_with_valid_values(int num1, int num2) {
assertDoesNotThrow(() -> calculator.divide(num1, num2));
}
private static Stream<Arguments> getValidValues() {
return Stream.of(
Arguments.of(1, 1),
Arguments.of(10, 2),
Arguments.of(-1, 1),
Arguments.of(-1, -1),
Arguments.of(0, 1)
);
}
We passed the String "getValidValues"
to the @MethodSource
that references to the new method that we have just created: private static Stream<Arguments> getValidValues()
This generates 5 different tests at the execution time, given that we are passing a Stream
containing 5 different Arguments
:
Some considerations:
- Method must be
static
. Otherwise, we will receive the error:org.junit.platform.commons.PreconditionViolationException: Cannot invoke non-static method
- When passing multiple arguments, we can use the class
Arguments
wrapped inStream
. - When passing a single argument, the method can just return a
Stream
of the required data type. For instance, if we want to passIntegers
the method would just returnStream<Integer>
.
private static Stream<Integer> getValidValues() {
return Stream.of(1, 2, -1, -2);
}
Conclusion
We learned how to make use of the Parameterized tests feature in Java. It’s a really simple, yet so powerful feature that every Java developer should know about.
The provided example was not complex at all so let me know if it would be useful to write another post to get deeper into the topic with more realistic examples. For a 101 introduction, it should be enough though.
If you’re interested, check out more TeachingDev Java posts!