Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

In this article, we’ll take a look at Spock, a Groovy testing framework. Mainly, Spock aims to be a more powerful alternative to the traditional JUnit stack, by leveraging Groovy features.

Groovy is a JVM-based language which seamlessly integrates with Java. On top of interoperability, it offers additional language concepts such as being a dynamic, having optional types and meta-programming.

By making use of Groovy, Spock introduces new and expressive ways of testing our Java applications, which simply aren’t possible in ordinary Java code. We’ll explore some of Spock’s high-level concepts during this article, with some practical step by step examples.

2. Maven Dependency

Before we get started, let’s add our Maven dependencies:

<properties>
    <spock-core.version>2.4-M1-groovy-4.0</spock-core.version>
    <groovy-all.version>4.0.16</groovy-all.version>
    <gmavenplus-plugin.version>3.0.2</gmavenplus-plugin.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.spockframework</groupId>
        <artifactId>spock-core</artifactId>
        <version>${spock-core.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.groovy</groupId>
        <artifactId>groovy-all</artifactId>
        <version>${groovy-all.version}</version>
        <type>pom</type>
    </dependency>
</dependencies>

We’ve added both Spock and Groovy as we would any standard library. In order to compile Groovy Code we need to include the gmavenplus plugin, and we have to adjust the configuration of the surefire plugin to enable it to find the Groovy test files:

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.gmavenplus</groupId>
            <artifactId>gmavenplus-plugin</artifactId>
            <version>${gmavenplus-plugin.version}</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compileTests</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <testSources>
                    <testSource>
                        <directory>${project.basedir}/src/test/groovy</directory>
                        <includes>
                            <include>**/*.groovy</include>
                        </includes>
                    </testSource>
                </testSources>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.2.2</version>
            <configuration>
                <testSourceDirectory>src/test/groovy</testSourceDirectory>
                <includes>
                    <include>**/*Specification.groovy</include>
                    <include>**/*Test.groovy</include>
                </includes>
            </configuration>
        </plugin>
    </plugins>
</build>

Now we are ready to write our first Spock test, which will be written in Groovy code. Note that we are using Groovy and Spock only for testing purposes and this is why those dependencies are test-scoped.

3. Structure of a Spock Test

3.1. Specifications and Features

As we are writing our tests in Groovy, we need to add them to the src/test/groovy directory, instead of src/test/java. Let’s create our first test in this directory, naming it Specification.groovy:

class FirstSpecification extends Specification {

}

Note that we are extending the Specification interface. Each Spock class must extend this in order to make the framework available to it. It’s doing so that allows us to implement our first feature:

def "one plus one should equal two"() {
  expect:
  1 + 1 == 2
}

Before explaining the code, it’s also worth noting that in Spock, what we refer to as a feature is somewhat synonymous to what we see as a test in JUnit. So whenever we refer to a feature we are actually referring to a test.

Now, let’s analyze our feature. In doing so, we should immediately be able to see some differences between it and Java.

The first difference is that the feature method name is written as an ordinary string. In JUnit, we would have had a method name which uses camelcase or underscores to separate the words, which would not have been as expressive or human readable.

The next is that our test code lives in an expect block. We will cover blocks in more detail shortly, but essentially they are a logical way of splitting up the different steps of our tests.

Finally, we realize that there are no assertions. That’s because the assertion is implicit, passing when our statement equals true and failing when it equals false. Again, we’ll cover assertions in more details shortly.

3.2. Blocks

Sometimes when writing JUnit a test, we might notice there isn’t an expressive way of breaking it up into parts. For example, if we were following behavior driven development, we might end up denoting the given when then parts using comments:

@Test
public void givenTwoAndTwo_whenAdding_thenResultIsFour() {
   // Given
   int first = 2;
   int second = 4;

   // When
   int result = 2 + 2;

   // Then
   assertTrue(result == 4)
}

Spock addresses this problem with blocks. Blocks are a Spock native way of breaking up the phases of our test using labels. They give us labels for given when then and more:

  1. Setup (Aliased by Given) – Here we perform any setup needed before a test is run. This is an implicit block, with code not in any block at all becoming part of it
  2. When – This is where we provide a stimulus to what is under test. In other words, where we invoke our method under test
  3. Then – This is where the assertions belong. In Spock, these are evaluated as plain boolean assertions, which will be covered later
  4. Expect – This is a way of performing our stimulus and assertion within the same block. Depending on what we find more expressive, we may or may not choose to use this block
  5. Cleanup – Here we tear down any test dependency resources which would otherwise be left behind. For example, we might want to remove any files from the file system or remove test data written to a database

Let’s try implementing our test again, this time making full use of blocks:

def "two plus two should equal four"() {
    given:
        int left = 2
        int right = 2

    when:
        int result = left + right

    then:
        result == 4
}

As we can see, blocks help our test become more readable.

3.3. Leveraging Groovy Features for Assertions

Within the then and expect blocks, assertions are implicit.

Mostly, every statement is evaluated and then fails if it is not true. When coupling this with various Groovy features, it does a good job of removing the need for an assertion library. Let’s try a list assertion to demonstrate this:

def "Should be able to remove from list"() {
    given:
        def list = [1, 2, 3, 4]

    when:
        list.remove(0)

    then:
        list == [2, 3, 4]
}

While we’re only touching briefly on Groovy in this article, it’s worth explaining what is happening here.

First, Groovy gives us simpler ways of creating lists. We can just able to declare our elements with square brackets, and internally a list will be instantiated.

Secondly, as Groovy is dynamic, we can use def which just means we aren’t declaring a type for our variables.

Finally, in the context of simplifying our test, the most useful feature demonstrated is operator overloading. This means that internally, rather than making a reference comparison like in Java, the equals() method will be invoked to compare the two lists.

It’s also worth demonstrating what happens when our test fails. Let’s make it break and then view what’s output to the console:

Condition not satisfied:

list == [1, 3, 4]
|    |
|    false
[2, 3, 4]
 <Click to see difference>

at FirstSpecification.Should be able to remove from list(FirstSpecification.groovy:30)

While all that’s going on is calling equals() on two lists, Spock is intelligent enough to perform a breakdown of the failing assertion, giving us useful information for debugging.

Grouping Assertions with the Same Target Object

Spock introduced a streamlined approach to grouping test conditions, inspired by Groovy’s Object.with method. This feature allows for the grouping of multiple assertions or interactions that involve the same target object within a with block, enhancing test readability and structure.

For instance, in testing a ShoppingCart object, you can briefly assert multiple properties:

class ShoppingCartTest extends Specification {
    def "verify multiple properties of a ShoppingCart"() {
        given:
        ShoppingCart cart = new ShoppingCart()
        cart.addItem("Apple", 3)
        cart.addItem("Banana", 2)

        expect:
        with(cart) {
            totalItems == 5
            totalPrice == 10.00
            items.contains("Apple")
            items.contains("Banana")
        }
    }
}

This grouping leads to more organized and understandable tests, especially beneficial in complex scenarios with multiple assertions on the same object.

3.4. Asserting Exceptions

Spock also provides us with an expressive way of checking for exceptions. In JUnit, some our options might be using a try-catch block, declare expected at the top of our test, or making use of a third party library. Spock’s native assertions come with a way of dealing with exceptions out of the box:

def "Should get an index out of bounds when removing a non-existent item"() {
    given:
        def list = [1, 2, 3, 4]
 
    when:
        list.remove(20)

    then:
        thrown(IndexOutOfBoundsException)
        list.size() == 4
}

Here, we’ve not had to introduce an additional library. Another advantage is that the thrown() method will assert the type of the exception, but not halt execution of the test.

4. Data Driven Testing

4.1. What Is a Data Driven Testing?

Essentially, data driven testing is when we test the same behavior multiple times with different parameters and assertions. A classic example of this would be testing a mathematical operation such as squaring a number. Depending on the various permutations of operands, the result will be different. In Java, the term we may be more familiar with is parameterized testing.

4.2. Implementing a Parameterized Test in Java

For some context, it’s worth implementing a parameterized test using JUnit:

@RunWith(Parameterized.class)
public class FibonacciTest {
    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {     
          { 1, 1 }, { 2, 4 }, { 3, 9 }  
        });
    }

    private int input;

    private int expected;

    public FibonacciTest (int input, int expected) {
        this.input = input;
        this.expected = expected;
    }

    @Test
    public void test() {
        assertEquals(fExpected, Math.pow(3, 2));
    }
}

As we can see there’s quite a lot of verbosity, and the code isn’t very readable. We’ve had to create a two-dimensional object array that lives outside of the test, and even a wrapper object for injecting the various test values.

4.3. Using Datatables in Spock

One easy win for Spock when compared to JUnit is how it cleanly it implements parameterized tests. Again, in Spock, this is known as Data Driven Testing. Now, let’s implement the same test again, only this time we’ll use Spock with Data Tables, which provides a far more convenient way of performing a parameterized test:

def "numbers to the power of two"(int a, int b, int c) {
  expect:
      Math.pow(a, b) == c

  where:
      a | b | c
      1 | 2 | 1
      2 | 2 | 4
      3 | 2 | 9
  }

As we can see, we just have a straightforward and expressive Data table containing all our parameters.

Also, it belongs where it should do, alongside the test, and there is no boilerplate. The test is expressive, with a human-readable name, and pure expect and where block to break up the logical sections.

4.4. When a Datatable Fails

It’s also worth seeing what happens when our test fails:

Condition not satisfied:

Math.pow(a, b) == c
     |   |  |  |  |
     4.0 2  2  |  1
               false

Expected :1

Actual   :4.0

Again, Spock gives us a very informative error message. We can see exactly what row of our Datatable caused a failure and why.

5. Mocking

5.1. What Is Mocking?

Mocking is a way of changing the behavior of a class which our service under test collaborates with. It’s a helpful way of being able to test business logic in isolation of its dependencies.

A classic example of this would be replacing a class which makes a network call with something which simply pretends to. For a more in-depth explanation, it’s worth reading this article.

5.2. Mocking Using Spock

Spock has it’s own mocking framework, making use of interesting concepts brought to the JVM by Groovy. First, let’s instantiate a Mock:

PaymentGateway paymentGateway = Mock()

In this case, the type of our mock is inferred by the variable type. As Groovy is a dynamic language, we can also provide a type argument, allow us to not have to assign our mock to any particular type:

def paymentGateway = Mock(PaymentGateway)

Now, whenever we call a method on our PaymentGateway mock, a default response will be given, without a real instance being invoked:

when:
    def result = paymentGateway.makePayment(12.99)

then:
    result == false

The term for this is lenient mocking. This means that mock methods which have not been defined will return sensible defaults, as opposed to throwing an exception. This is by design in Spock, in order to make mocks and thus tests less brittle.

5.3. Stubbing Method Calls on Mocks

We can also configure methods called on our mock to respond in a certain way to different arguments. Let’s try getting our PaymentGateway mock to return true when we make a payment of 20:

given:
    paymentGateway.makePayment(20) >> true

when:
    def result = paymentGateway.makePayment(20)

then:
    result == true

What’s interesting here, is how Spock makes use of Groovy’s operator overloading in order to stub method calls. With Java, we have to call real methods, which arguably means that the resulting code is more verbose and potentially less expressive.

Now, let’s try a few more types of stubbing.

If we stopped caring about our method argument and always wanted to return true, we could just use an underscore:

paymentGateway.makePayment(_) >> true

If we wanted to alternate between different responses, we could provide a list, for which each element will be returned in sequence:

paymentGateway.makePayment(_) >>> [true, true, false, true]

There are more possibilities, and these may be covered in a more advanced future article on mocking.

5.4. Improved Mock Declaration

Since Spock 2.0, We can declare the interactions at mock creation, enhancing the readability and maintainability of tests. This feature is particularly beneficial when our Mocks are not used across multiple tests, or their setup is the same across multiple tests. It allows us to define the behavior of mocks concisely right where the mock is instantiated.

Let’s consider an example of BookService being tested, the new feature would allow a mock BookRepository to be created and its interactions defined in one place:

class BookServiceTest extends Specification {
    def "should retrieve book details and verify method calls"() {
        given:
        def bookRepository = Mock(BookRepository) {
            findById(1L) >> new Book("Effective Java", "Joshua Bloch")
            findById(2L) >> null
        }
        def bookService = new BookService(bookRepository)

        when:
        Book effectiveJava = bookService.getBookDetails(1L)
        Book unknownBook = bookService.getBookDetails(2L)

        then:
        1 * bookRepository.findById(1L)
        1 * bookRepository.findById(2L)
        effectiveJava.title == "Effective Java"
        unknownBook == null
    }
}

This approach directly links the behavior of the mock BookRepository to its creation, making the test more structured and intuitive. This is just one of the ways Spock 2.3 improves the testing experience, emphasizing clarity and efficiency.

5.5. Verification

Another thing we might want to do with mocks is assert that various methods were called on them with expected parameters. In other words, we ought to verify interactions with our mocks.

A typical use case for verification would be if a method on our mock had a void return type. In this case, by there being no result for us to operate on, there’s no inferred behavior for us to test via the method under test. Generally, if something was returned, then the method under test could operate on it, and it’s the result of that operation would be what we assert.

Let’s try verifying that a method with a void return type is called:

def "Should verify notify was called"() {
    given:
        def notifier = Mock(Notifier)

    when:
        notifier.notify('foo')

    then:
        1 * notifier.notify('foo')
}

Spock is leveraging Groovy operator overloading again. By multiplying our mocks method call by one, we are saying how many times we expect it to have been called.

If our method had not been called at all or alternatively had not been called as many times as we specified, then our test would have failed to give us an informative Spock error message. Let’s prove this by expecting it to have been called twice:

2 * notifier.notify('foo')

Following this, let’s see what the error message looks like. We’ll that as usual; it’s quite informative:

Too few invocations for:

2 * notifier.notify('foo')   (1 invocation)

Just like stubbing, we can also perform looser verification matching. If we didn’t care what our method parameter was, we could use an underscore:

2 * notifier.notify(_)

Or if we wanted to make sure it wasn’t called with a particular argument, we could use the not operator:

2 * notifier.notify(!'foo')

Again, there are more possibilities, which may be covered in a future more advanced article.

5.6. Global Mocks

Spock 2.0 also introduced the possibility of replacing all instances of a class with a Mock globally, allowing us to mock objects that we are not directly instantiating in the Tests. It simplifies testing by removing the need for injecting the Mocks explicitly into the code we are testing:

public class UtilityClass {
    public static String getMessage() {
        return "Original Message";
    }
}

public class MessageService {
    public String fetchMessage() {
        return UtilityClass.getMessage();
    }
}

In the above code, we have a class using another, writing tests for MessageService with a UtilityClass mock is difficult because it is not designed to allow us to inject a mock for it.

This is when Global Mocks come more than handy:

class MessageServiceTest extends Specification {
    def "should use global mock for UtilityClass"() {
        given:
        def utilityMock = GroovySpy(UtilityClass, global: true)
        utilityMock.getMessage() >> "Mocked Message"

        when:
        MessageService service = new MessageService()
        String message = service.fetchMessage()

        then:
        1 * utilityMock.getMessage()
        message == "Mocked Message"
    }
}

In the above example, we create a Global Mock using the GroovySpy class, note that we do not mock MessageService or inject the mock UtilityClass in any way, nevertheless, we can successfully assert expectations on the mock, or that we obtained the value we stubbed instead of the original one.

6. Conclusion

In this article, we’ve given a quick slice through testing with Spock.

We’ve demonstrated how, by leveraging Groovy, we can make our tests more expressive than the typical JUnit stack. We’ve explained the structure of specifications and features.

And we’ve shown how easy it is to perform data-driven testing, and also how mocking and assertions are easy via native Spock functionality.

The implementation of these examples can be found over on GitHub. This is a Maven-based project, so should be easy to run as is.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.