Writing parameterized unit tests in Java with JUnit 5— Run the same test with various inputs

Aaron Moore
3 min readSep 2, 2023
Photo by Caspar Camille Rubin on Unsplash

I’ve written a similar article for C#. However, lately I’ve been coding more in Java and thought this same article would be useful for that audience as well. Unit testing is often (unfortunately) an afterthought or something done in haste. However, it’s important that testing be impactful and maintainable. One of the best ways to do that is to write a single test which will support multiple test cases through parameters. This also allows the tests that we write to be more robust as we can run through more inputs than we would otherwise.

Say, for example, we want to implement a unit test for a basic FizzBuzz. For this example I’m using the following specification:

Write a function that accepts a single int as input. For multiples of ‘3’ print “Fizz”, for the multiples of ‘5’ print “Buzz”, and for any other number, simply print the number itself.

Now, we’re going to write a unit test for this but we’re going to parameterize it. For these examples we’re going to use JUnit 5 and supply a variety of parameters to test with. In Java with JUnit it would look something like this:

@ParameterizedTest
@ValueSource(ints = {1, 2, 4, 7, 8, 11})
public void fizzBuzz_numeric(int input) {
assertEquals(String.valueOf(input), underTest.fizzBuzz(input));
}
@ParameterizedTest
@ValueSource(ints = {3, 6, 9, 12})
public void fizzBuzz_fizz(int input) {
assertEquals("Fizz", underTest.fizzBuzz(input));
}
@ParameterizedTest
@ValueSource(ints = {5, 10, 20})
public void fizzBuzz_buzz(int input) {
assertEquals("Buzz", underTest.fizzBuzz(input));
}
@ParameterizedTest
@ValueSource(ints = {15, 30, 45})
public void fizzBuzz_fizzBuzz(int input)
{
assertEquals("FizzBuzz", underTest.fizzBuzz(input));
}

Each of these take a decent representation of the inputs and make sure they match the expected outputs.

There are a lot of different source inputs for parameterized tests in Java. Just to name a few:

@NullSource
@EmptySource
@NullAndEmptySource
@EnumSource
@CsvSource

But the one I want to focus on is one that gives a tremendous amount of freedom in the way that you can parameterize your test and that is @MethodSource

@ParameterizedTest(name = "{0} is expected to output {1}")
@MethodSource("fizzBuzzTestCases")
public void fizzBuzz(int input, string expected) {
assertEquals(expected, underTest.fizzBuzz(input));
}

private static Stream<Arguments> fizzBuzzTestCases() {
return Stream.of(
Arguments.of(named.of(1, "1 - not divisible by 3 or 5"), "1"),
Arguments.of(named.of(2, "2 - not divisible by 3 or 5"), "2"),
Arguments.of(named.of(3, "3 - divisible by 3"), "Fizz"),
Arguments.of(named.of(4, "4 - not divisible by 3 or 5"), "4"),
Arguments.of(named.of(5, "5 - divisible by 5"), "Buzz"),
Arguments.of(named.of(6, "6 - divisible by 3"), "Fizz"),
Arguments.of(named.of(10, "10 - divisible by 5"), "Buzz"),
Arguments.of(named.of(11, "11 - not divisible by 3 or 5"), "11"),
Arguments.of(named.of(15, "15 - divisible by both 3 and 5"), "FizzBuzz")
);
}

Note that these test results include a separate line for each set of inputs. Of note that I’ve done above is adding custom names to each case that print out in a friendly manner both the input and the reasoning behind the arguments. This will display in the test summary.

Summary

It’s important to test, but just as important for those tests to actually be useful. Write your tests in a way that encompass a variety of inputs, can be read and maintained by your peers, and are clear in their purpose and scope.

--

--