Parameterized unit testing in Java

Occasionally (maybe even often) you’ll need some way to run the same unit test code against multiple inputs.

For example, you might have some code that iterates over a string counting certain characters (let’s say it counts the letter Z), the unit test would be exactly the same for testing the following scenarios

  1. When no characters of the expected type exist
  2. When characters of the expected type exist
  3. When the string is empty
  4. When the string is null

The only difference would be the input to the unit test and the expectation for the assert. In such situations we would tend to use parameterized unit tests, in C#/NUnit this would be with TestCase attribute. In Java with JUnit 5 (org.junit.jupiter.*) this would be with the @ParameterizedTest annotation.

We’re going to need to add the following dependency to our pom.xml (change the version to suit).

<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter-params</artifactId>
  <version>5.8.1</version>
  <scope>test</scope>
</dependency>

We could write two unit tests for the above scenario, one for success and one for failure, such as when no characters exist (i.e. the string is null or empty we expect a 0 return from our system under test), a second test would maybe check a known return value. In such situations we can simply use something like

@ParameterizedTest
@ValueSource(strings = {"", null, "aaabbb" })
void noZExists_ShouldReturn_Zero(string input) {
    assertEqual(0, CharacterCounter.countZ(input));
}

Now we’d have another unit test for successful cases, for example

@ParameterizedTest
@ValueSource(strings = {"aaaaZZZ", "ZZZ", "ZZZaaa" })
void zExists_ShouldReturn_Three(string input) {
    assertEqual(3, CharacterCounter.countZ(input));
}

This would be far better if we simply wrote one unit test but could pass in both the input as well as the expected result, hence combine all values into a single test. The only option I found for this was to use the @CvsSource annotation, so for example we write the input followed by the comma separate followed by the expectation – ofcourse we could supply more than two args per call of the unit test, but this is adequate for our needs – this means our test would look more like this

@ParameterizedTest
@CsvSource({ "\"\",0", "aaabbb,0", "aaaaZZZ,3", "ZZZ,3", "ZZZaaa,3" })
void zExists_ShouldReturn_Three(string input, int expectation) {
    assertEqual(expectation, CharacterCounter.countZ(input));
}