How to test… Java with Groovy & Spock

In November 2013 I wrote an article about the advantages of Groovy as an alternative test language for Java. While the article shows some clear advantages of the Groovy language, there are some strictly test related features that are not build into Groovy.

In this article I want to introduce the Spock framework which provides these features very nicely and turns Groovy into a real testing domain specific language.

Getting Started with Maven

To use Spock for testing in our Maven project we only need to add it to our dependency list. Additionally I recommend cglib and Objenesis so Spock will be able to mock any non-final class, even if it has no interface and no constructor.

Starting form the groovy-testing example pom we end up with this one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>de.assertagile.demonstration</groupId>
    <artifactId>spock-testing</artifactId>
    <version>1.0-SNAPSHOT</version>
 
    <licenses>
        <license>
            <name>Apache License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
        </license>
    </licenses>
 
    <developers>
        <developer>
            <id>mkutz</id>
            <name>Michael Kutz</name>
            <email>mail@assertagile.de</email>
            <timezone>+1</timezone>
            <roles>
                <role>developer</role>
                <role>tester</role>
            </roles>
        </developer>
    </developers>
 
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
 
    <prerequisites><maven>3.0</maven></prerequisites>
 
    <dependencies>
        <dependency>
            <groupId>org.spockframework</groupId>
            <artifactId>spock-core</artifactId>
            <version>1.1-groovy-2.4-rc-3</version>
            <scope>test</scope>
        </dependency>
 
        <!-- CGLib enables mocking of classes (in addition to interfaces) -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>3.2.4</version>
            <scope>test</scope>
        </dependency>
        <!-- Objenesis enables mocking of classes without default constructor (together with CGLib) -->
        <dependency>
            <groupId>org.objenesis</groupId>
            <artifactId>objenesis</artifactId>
            <version>2.5.1</version>
            <scope>test</scope>
        </dependency>
 
 
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.4.8</version>
        </dependency>
 
    </dependencies>
 
    <build>
        <testSourceDirectory>src/test/groovy</testSourceDirectory>
 
        <pluginManagement>
            <plugins>
 
                <!-- Configure the compiler plugin for Groovy -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.6.1</version>
                    <extensions>true</extensions>
                    <configuration>
                        <compilerId>groovy-eclipse-compiler</compilerId>
                        <verbose>false</verbose>
                        <source>${java.version}</source>
                        <target>${java.version}</target>
                        <encoding>${project.build.sourceEncoding}</encoding>
                    </configuration>
                    <dependencies>
                        <dependency>
                            <groupId>org.codehaus.groovy</groupId>
                            <artifactId>groovy-eclipse-compiler</artifactId>
                            <version>2.9.2-01</version>
                        </dependency>
                        <dependency>
                            <groupId>org.codehaus.groovy</groupId>
                            <artifactId>groovy-eclipse-batch</artifactId>
                            <version>2.4.3-01</version>
                        </dependency>
                    </dependencies>
                </plugin>
 
                <!-- Spock tests end with "Spec" -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.19.1</version>
                    <configuration>
                        <includes>
                            <include>**/*Test.*</include>
                            <include>**/*Spec.*</include>
                        </includes>
                    </configuration>
                </plugin>
 
                <!-- Build Helper: will add Groovy sources to path -->
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>build-helper-maven-plugin</artifactId>
                    <version>3.0.0</version>
                    <executions>
                        <execution>
                            <id>add-source</id>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>add-source</goal>
                            </goals>
                            <configuration>
                                <sources>
                                    <source>${basedir}/src/main/java</source>
                                    <source>${basedir}/src/main/groovy</source>
                                </sources>
                            </configuration>
                        </execution>
                        <execution>
                            <id>add-test-source</id>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>add-test-source</goal>
                            </goals>
                            <configuration>
                                <sources>
                                    <source>${basedir}/src/test/groovy</source>
                                    <source>${basedir}/src/test/java</source>
                                </sources>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
 
            </plugins>
        </pluginManagement>
    </build>
</project>

Why use it?

You can read the documententation for detailed information. There is also a nice E-Book published, which you can download at leanpub.com. Here I will only give some examples of really cool features.

Feature Methods

Feature methods are Spock's test methods. They describe a feature of the test object. A really nice idea of Spock is that feature method's names may be any string. There is no need for endless camel casing, we can write our minds as you can see in the examples in this article.

Blocks

In feature methods there are blocks to structure the specification into differed phases:

given or setup
should hold code that sets the stage for the test.
when
holds the stimulus code triggering some aspect of the test object. Spock will ensure that all setup is done before this block's code is executed even if it was not explicitly written in a given or setup block.
then
contains the verification code. Spock makes each line in this block a condition, that will make the test fail if the condition's return value is boolean false. So we don't need to write asserts or even use an assertion framework.
expect
is a combination of when and then. Just like when, Spock will ensure all setup is finished before its code gets executed and will fail the test if any line returns a boolean false. This is useful for rather simple tests.
cleanup
may contain cleanup code that is needed for the feature test but not for each feature in the specification. Like the cleanup fixture method, it will be executed regardless of the test's result or of any exception was thrown.

where
is the strangest of all blocks. It may define test data to parametrize the test and thereby enable data driven testing (see below).

Here are some examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package de.assertagile.demonstration.howtotest.spock
 
import spock.lang.Specification
 
class BlocksDemoSpec extends Specification {
 
    def "given, when and then make tests more structured"() {
        given:
        int testValue = 1
 
        when:
        testValue++
 
        then:
        testValue == 2
    }
 
    def "expect combines when and then"() {
        expect:
        1 != 2
    }
 
    def "blocks can also change the execution order"() {
        given:
        int testValue = 1 // as expected given is executed first
 
        when:
        testValue++ // when is executed AFTER old() was called!
 
        then:
        testValue == old(testValue) + 1 // old is executed BEFORE when to record the original value!
    }
}
BlocksDemoSpec.groovyview rawview file on GitHub

See also the Spock docs.

Specifying Exception Behaviour

Testing exception behaviour is one thing that is often forgotten by developers. I think getting an Euro for every uncovered catch or finally block I ever saw would make me a rich man.

Spock makes exception testing very, very easy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package de.assertagile.demonstration.howtotest.spock
 
import spock.lang.Specification
 
class ExceptionTestingDemoSpec extends Specification {
 
    def "testing exception behaviour with spock is great too"() {
        when:
        new File("some/place/no/file/can/be/found/at").append("text")
 
        then:
        thrown(FileNotFoundException)
    }
 
    def "you can also verify the exception since it is returned by thrown"() {
        given:
        String wrongPath = "some/place/no/file/can/be/found/at"
 
        when:
        new File(wrongPath).append("text")
 
        then:
        FileNotFoundException e = thrown()
 
        and:
        e.message ==~ /^${wrongPath}.+/
    }
}
ExceptionTestingDemoSpec.groovyview rawview file on GitHub

Way more intuitive than an annotation the method or a try-catch-block, right?

See also the Spock docs.

Data Driven Feature Testing

Sometimes you have tests which need to be executed with a range of values. Spock has a nice syntax to do data driven testing. Here are some simple examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package de.assertagile.demonstration.howtotest.spock
 
import spock.lang.Specification
import spock.lang.Unroll
 
class DataDrivenTestingDemoSpec extends Specification {
 
    def "where blocks for data driven testing"(int testValue) {
        when:
        testValue++
 
        then:
        testValue == old(testValue) + 1
 
        where:
        testValue << [1, 100, 999999999]
    }
 
    @Unroll
    def "unroll gives you more information what caused a test to fail"(int testValue) {
        when:
        testValue++
 
        then:
        testValue == old(testValue) + 1
 
        where:
        testValue << [1, 100, 999999999]
    }
 
    @Unroll("incrementing #testValue should add 1 to it")
    def "you can also modify the generated tests' names"(int testValue) {
        when:
        testValue++
 
        then:
        testValue == old(testValue) + 1
 
        where:
        testValue << [1, 100, 999999999]
    }
 
    @Unroll("incrementing #testValue should set it to #expectedValue")
    def "where blocks can also be in table form"(int testValue, int expectedValue) {
        when:
        testValue++
 
        then:
        testValue == expectedValue
 
        where:
        testValue | expectedValue
        1         | 2
        100       | 101
        999999999 | 1000000000
    }
}
DataDrivenTestingDemoSpec.groovyview rawview file on GitHub

See also the Spock docs.

Specifying Interactions/Mocking

Mocking is often needed when writing class tests, since we do not want to test any other class but one (see my previous post about class tests) we will need to replace any class used by that one with mocks. Also we want to know if the class is using the other classes as specified.

Spock has its own mocking and verification abilities:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package de.assertagile.demonstration.howtotest.spock
 
import spock.lang.Ignore
import spock.lang.Specification
 
class MockingDemoSpec extends Specification {
 
    def "mocking with spock is easy"() {
        given:
        String expectedString = "abc"
 
        and:
        Object objectMock = Mock()
        objectMock.toString() >> expectedString
 
        expect:
        "${objectMock}" == expectedString
    }
 
    def "verification is quite legible"() {
        given:
        Object objectMock = Mock()
        1 * objectMock.toString()
 
        expect:
        "${objectMock}"
    }
 
    @Ignore("this does not work since the verification overwrites the mocking")
    def "since verification and mocking is the same in spock, be careful"() {
        given:
        String expectedString = "abc"
 
        and:
        Object objectMock = Mock() {
            toString() >> expectedString
        }
 
        when:
        String string = "${objectMock}"
 
        then:
        string == expectedString
 
        and:
        1 * objectMock.toString() // by the way: this is executed BEFORE the when block!
    }
 
 
    def "argument matching"() {
        given:
        String expectedArgument = "abc"
 
        and:
        List<String> listMock = Mock()
 
        when:
        listMock.contains(expectedArgument)
 
        then:
        1 * listMock.contains(expectedArgument)
    }
 
    def "complex argument matching"() {
        given:
        Map<String, Object> mapMock = Mock()
 
        when:
        mapMock.put("bla", ["a", "b", "c"])
 
        then:
        1 * mapMock.put(_, { it[1] == "b" })
    }
 
    def "get complex arguments for closer inspection"() {
        given:
        List<String> extractedArgument = null
        Map<String, Object> mapMock = Mock()
 
        when:
        mapMock.put("bla", ["a", "b", "c"])
 
        then:
        1 * mapMock.put(_ as String, { List<Map> it -> extractedArgument = it })
        extractedArgument.contains("c")
        extractedArgument.size() == 3 // => better assertion feedback
    }
}
MockingDemoSpec.groovyview rawview file on GitHub

See also the Spock docs.

Comments & Reporting

Any block can be followed by a string as comment. By default these strings just make the test legible but they can also be used to generate reports using the (currently still not official) Spock Reports extension. There also might be some official support in the future as Peter suggested in his comment.

Documentation & Information

The project started out with a Google Code page still linked to its domain even though the project development has moved to GitHub some time ago. All of the following links can be found on the Google Code page as well, but since they get overlooked sometimes, here are the most important sources of information about Spock: