Data driven testing with Spock and JUnit5: Who wins?

I’d been a Java developer for some years when in my previous company we started using Groovy and a testing framework “that makes things easier to test” as my more experienced peers used to tell me. If you’re familiar with Groovy, then you know what the advantages are in terms of streams, lists, maps, closures, getters/setters out of the box via reflection (there goes privacy), etc. Some companies will use Groovy for their main development and tests or other will use it only for the latter, in terms of setting up easily test methods, helper functions, no builders needed, etc. Like you’d do with Kotlin.

Though this article is not here to discuss whether Groovy wins or loses as compared to Java, the former can be combined as the language means to a very powerful testing framework (and my favourite one), Spock. For an introduction and how you can use it, please visit the official documentation page and an Introduction to Testing with Spock and Groovy.

When I started to write tests in Spock, I was amazed with how easy it is to set up and write a paramerized test, i.e. one test or one template if you will, where you specify varying inputs in a multiline block. E.g.

import spock.lang.Specification
import spock.lang.Unrollclass MathTest extends Specification {  @Unroll
  def "sum of #a and #b equals #expectedResult"() {
    
    when:
    def actualResult = a + b    then:
    expectedResult == actualResult    where:
    a | b | expectedResult
    0 | 0 | 0
    0 | 1 | 1
    1 | 2 | 3
  }}

For more information, please visit Data Driven Testing section of Spock documentation.

In Junit5, you can do the same, albeit not that easily. Let’s see how it goes:

You replace @Test, with @ParameterizedTest. This annotation is a successor of Junit4 annotation: @RunWith(Parameterized.class)

class MathTest {  @ParameterizedTest
  @CsvFileSource(resources = "numbers.csv", numLinesToSkip = 1)
  void testsForSum(int a, int b, int expectedResult) {
    assertEquals(expectedResult, Math.addExact(a, b));
  }}

OR:

class MathTest {  @ParameterizedTest
  @MethodSource("populateIntegers")
  void testsForSum_2(int a, int b, int expectedResult) {
    assertEquals(expectedResult, Math.addExact(a, b));
  }  private static Stream<Arguments> populateIntegers() {
    return Stream.of(
      Arguments.of(0, 0, 0),
      Arguments.of(0, 1, 1),
      Arguments.of(1, 2, 3));
  }}

where numbers.csv is:

a,b,expected
0,0,0
0,1,1
1,2,3

For more info, please visit a comprehensive Guide to JUnit 5 Parameterized Tests  and Using JUnit 5 with Gradle.

In terms of lines or difficulty, JUnit5 seems to be really intuitive (if you get the gist of naming and basic building blocks for parameterised tests).

The complexity is increased when we want to test more complicated varying use cases.

In Spock, the “where” block is easier to construct. It can even take methods as parameters as long as they’re static.

JUnit 5 vs. Spock feature showdown offers a more exhaustive comparison.

Finally, please visit playground and playground-junit GitHub repositories that investigate the same use case scenario, using Groovy/Spock and Java/JUnit5 respectively. Both repos have the same Colour class, playground one has a Groovy/Spock based ColourTest class, while playground-junit has a Java/JUnit5 based ColourTest class. They follow almost identical test cases in order to be able to show and compare the logical flow and different syntax when writing data driven tests.