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?
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.
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);
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.