parameterized-tests

Parameterized tests (JUnit 4 vs JUnit 5 vs Spock)

Parameterized tests allow you testing combinations of parameters by executing the same test method with different values.

In which scenarios such technique can be useful?

Let’s say that we have to implement a conversion of a single roman number to Arabic number.

For the simplicity of the example, we’re not introducing any domain objects / validation etc. The class responsible for the conversion looks as following:

public class SingleRomanNumberConverter {
    public int convert(String romanNumber) {
        // ...
    }	
}

How can we test it? We could write a test for each roman number that would look something like this:

@Test
public void converts_I_Number() {
    assertThat(converter.convert("I")).isEqualTo(1);
}

@Test
public void converts_V_Number() {
    assertThat(converter.convert("V")).isEqualTo(5);
}
	
// ...

As you can see it doesn’t scale well.

So maybe let’s have one test with assertions for each roman number?

@Test
public void convertsAllSingleNumbers() {
    assertThat(converter.convert("I")).isEqualTo(1);
    assertThat(converter.convert("V")).isEqualTo(5);
	//...
}

There are a couple of issues with this approach. First of all, if one assertion fails, other assertions are not executed. We don’t know straight away which one failed and if others would be successful. Secondly, if there are many test cases, the method becomes not readable.

Let’s see how parameterized tests solve these issues in popular Java testing frameworks.

JUnit 4

If you’re stuck with JUnit 4 because you can’t introduce a newer framework to your codebase, you don’t want to mix testing approaches, etc., you should use JUnitParams. There is a native JUnit 4 support for parameterized tests but it’s not developer friendly and I don’t recommend it.

Here’s how the test with JUnitParams looks like:

@Test
@Parameters({
            "I, 1",
            "V, 5",
            "X, 10",
            "L, 50",
            "C, 100",
            "D, 500",
            "M, 1000",
})
public void convertsAllSingleNumbers(String romanNumber, int expectedResult) {
    assertThat(converter.convert(romanNumber)).isEqualTo(expectedResult);
}

As you can see above, we can use @Parameters annotation where we specify the parameters (as an array) which are injected as romanNumber and expectedResult parameters of our test method.

JUnit 5

The latest version of JUnit has nice support for parameterized tests:

@ParameterizedTest
@CsvSource({
        "I, 1",
        "V, 5",
        "X, 10",
        "L, 50",
        "C, 100",
        "D, 500",
        "M, 1000"
})
void convertsAllSingleNumbers(String romanNumber, int expectedResult){
    assertThat(converter.convert(romanNumber)).isEqualTo(expectedResult);
}

We can use @CsvSource annotation (it’s very similar to @Parameters annotation from previous example).

Spock

I greatly recommend using Spock as your test framework for Java code (not only for parameterized tests but in general). Currently it gives the most flexibility and notably boosts your productivity.

The biggest disadvantage is that you need to introduce different language to your codebase, but Groovy has a quite low learning curve for Java developer and you can limit the usage scope only for tests.

Let’s see what we can do with Spock:

@Unroll('converts #romanNumber to #expectedResult')
def 'converts all single numbers'() {
    expect:
    converter.convert(romanNumber) == expectedResult

    where:
    romanNumber | expectedResult
    "I"                    | 1
    "V"                   | 5
    "X"                   | 10
    "L"                   | 50
    "C"                   | 100
    "D"                   | 500
    "M"                  | 1000
    }

As you can see the parameters are in a form of a table that is readable and easily extensible (more info here). The method name can be written as the whole sentence which is also very user-friendly. The @Unroll annotation will make each test iteration visible in your IDE and allows displaying custom name.

Conclusion

If you don’t want to use another language than Java in your project or you don’t want to use latest JUnit, you can still write parameterized tests in a quite nice form using JUnit 4. However, newer frameworks are easier and more flexible to use.

You can find full source code with all examples from this article here.

Note: Special thanks to Christian, who showed us that JUnit 5 params support multi values via CSV notation.

Join our team:

SENIOR
JAVA
DEVELOPER

SENIOR
BLOCKCHAIN
DEVELOPER

SENIOR
C++
DEVELOPER



LEAVE A COMMENT