Embracing BDD with Spock, a worthy JUnit alternative?

As (Java) developers, testing is a critical part of our workflow, and while JUnit has been a reliable tool, Spock is definitely worth a look. With its use of Groovy and focus on BDD (Behavior-Driven Development), Spock enables us to write comprehensive tests that are easy to understand at a glance – a significant advantage for teams looking to maintain and scale their testing efforts. Let’s take a look at Spock and how it sets itself apart from other testing frameworks like JUnit! (Daan de Rond/Developer by Kabisa)

What is Spock?

Spock is a testing and specification framework for Java and Groovy applications that embodies the principles of BDD (Behavior-Driven Development). BDD is an approach to software development that encourages collaboration between developers, QA and non-technical/business participants in a software project. Spock enables you to write tests that are not only technical representations of the code’s behavior, but also a form of communication.
def "Introducing Java developers to Spock through a blogpost might convince them to give it a try"() {
  given: "Someone from Kabisa writes a blogpost"
  when: "Spock's features are highlighted and compared to JUnit"
  then: "Someone reading the blogpost might give Spock a try"
}

What makes it stand out is its expressive power and ease of use. It incorporates ideas from other testing frameworks and languages, offering features such as mocking, stubbing and a rich set of matchers. This makes Spock an incredibly powerful tool for writing both simple and complex tests.

Groovy

Groovy is a dynamic, object-oriented programming language for the Java platform that offers a more concise and expressive syntax than Java.. It integrates seamlessly with all existing Java code and runs on the JVM. Most importantly, a professional with junior-level experience should have no issues creating tests in Groovy. Groovy’s syntax is easy to understand and more concise than Java, which means your test code is not only shorter but also more readable.

In practice

Integrating Spock into your Java project is straightforward. Spock is built with Maven, which means adding it to your project is as simple as including the dependency in your pom or build.gradle.
While JUnit has been the standard for testing in the Java ecosystem, Spock offers a few enhancements. Let’s take a look at some examples and in some cases compare them with JUnit.

Expressive and readable

Spock transforms tests into documentation, clarifying intent and behavior through its structured “given-when-then” format. With JUnit the same can be achieved with regular comments, but in my experience these are often neglected.

Spock

def "adding two numbers has the expected outcome"() {
  given: "a calculator"
  Calculator calculator = new Calculator()

  when: "two numbers are added"
  def result = calculator.add(2, 3)

  then: "the result is the sum of the two numbers"
  result == 5
}

JUnit

@Test
public void addTwoNumbers() {
    // a calculator
    Calculator calculator = new Calculator();

    // two numbers are added
    int result = calculator.add(2, 3);

    // the result is the sum of the two numbers
    assertEquals(5, result);
}

Test Execution

Spock enables us to clearly document the way we expect the class we are testing to behave. This in turn provides better maintainability and a better understanding of the test itself and the code we are trying to test. Let’s take a look at test execution inside our IDE (IntelliJ in this case) with some Spock tests.

Clearly stating what is expected to happen, in normal “human” language. Now what would the same test execution look like with JUnit?

While JUnit has been the standard for testing in the Java ecosystem, Spock offers a few enhancements. Let’s take a look at some examples and in some cases compare them with JUnit.

In this case I personally prefer Spock’s expressiveness.

Parameterized tests

Allows you to write a single test that can cover multiple scenarios. While JUnit also added parameterized tests, Spock’s approach is more readable and elegant in my opinion.

Spock

@Unroll   /* Each row in the table is executed as separate test */
def "should return #result when #a is added to #b"() {
       expect:
       Math.addExact(a, b) == result

       where:
       a | b || result
       1 | 2 || 3
       5 | 5 || 10
       2 | 8 || 10
   }

JUnit

@ParameterizedTest
@CsvSource({
    "1, 2, 3",
    "5, 5, 10",
    "2, 8, 10"
})
void shouldReturnResultWhenAIsAddedToB(int a, int b, int expectedResult) {
    Assertions.assertEquals(expectedResult, Math.addExact(a, b));
}

Mocking and Stubbing

With Spock, the terms “stub” and “mock” refer to different types of test doubles used to replace the real objects during testing. It does not depend on other libraries such as Mockito for stubbing and mocking.

  • Stubs provide predefined responses to method calls, they are used to isolate the class you are testing from external dependencies.
def userRepository = Stub(UserRepository.class);

// when stub is called with any argument (_), new User is returned.
userRepository.findById(_) >> new User(1, "Kabisa")
  • Mocks not only provide predefined responses, but they also record how they were interacted with during the test. With mocks you can verify whether certain methods were called, how often they were called and what arguments they were called with.
def userRepository = Mock(UserRepository.class);

// Verify's that the userRepository is called once with the id '5'
1 * userRepository.findById(5);
Of course these are just a couple comparisons and Spock features. For more you can check out the official documentation here and take a look at Spock’s GitHub here.

Conclusion

While JUnit (5!) remains a solid choice, my personal preference leans towards Spock. It’s a combination of Groovy’s syntax and Spock’s features that help in writing cleaner and more descriptive tests. Although I can understand people being hesitant to use a different language than their application code for testing. I can personally attest that using Groovy for the tests isn’t really an issue, its similarities to Java make the transition almost seamless.

All in all, JUnit and Spock are both great testing frameworks and it ultimately boils down to personal preference. Hopefully this post showed you that JUnit surely isn’t the only way to write tests for your Java applications.