In this blog entry we will start with a simple Spock specification for a Groovy class we create. We can learn why to use Spock on the Spock website. In this article we show with code how our tests will work with Spock. To get started with this sample we need Gradle installed on our machine and nothing else. The current release of Gradle at the time of writing this entry is 0.9-preview-3.
First we need to create a new directory for our little project and then we create a new build.gradle
file:
$ mkdir spock-intro $ cd spock-intro $ mkdir -p src/main/groovy/com/mrhaki/blog src/test/groovy/com/mrhaki/blog
// File: build.gradle apply plugin: 'groovy' repositories { mavenCentral() } dependencies { groovy 'org.codehaus.groovy:groovy:1.7.3' testCompile 'org.spockframework:spock-core:0.4-groovy-1.7' }
Next we create our tests, but in Spock they are called specifications. We only need to extend the spock.lang.Specification
and we get all the Spock magic in our hands. We start simple by defining a specification where we want to count the number of users in a UserService
class. We are going to create the UserService
class later, we start first with our specification:
package com.mrhaki.blog import spock.lang.Specification class UserServiceSpecification extends Specification { def "Return total number of users"() { setup: 'Create UserService instance with 2 users' UserService userService = new UserService(users: ['mrhaki', 'hubert']) expect: 'Invoke count() method' 2 == userService.count() } }
Notice at line 6 how we can use very descriptive method names by using String literals. Next we create an instance of the UserService
class and pass a list of two users at line 8. And then we check if the return value is the expected value 2 with a simple assertion statement. Spock provides a very readable way to write code. Mostly we first setup code for testing, run the code and finally test the results. This logic is supported nicely by Spock by using the labels setup
and expect
. Later on we see more of these labels.
Before we run the test we create our UserService
class:
package com.mrhaki.blog class UserService { Collection<String> users int count() { users ? users.size() : 0 } }
We can run our code and test with the following command:
$ gradle test :compileJava UP-TO-DATE :compileGroovy :processResources UP-TO-DATE :classes :compileTestJava :compileTestGroovy :processTestResources UP-TO-DATE :testClasses :test BUILD SUCCESSFUL Total time: 12.475 secs
The source files are compiled and our specification is run. We get the BUILD SUCCESSFUL
message indicating our test runs fine. If the test would fail we can open build/reports/tests/index.html
or build/test-results/TEST-com.mrhaki.blog.UserServiceSpecification.xml
to see the failure.
We specified the count()
method must return the number of users, but we only check it for 2 elements, but what if we want to test to see if 0 and 1 user also return the correct count value? We can create new methods in our specification class, but Spock makes it so easy to do this elegantly:
package com.mrhaki.blog import spock.lang.Specification class UserServiceSpecification extends Specification { def "Return total number of users"() { setup: 'Create UserService instance with users' UserService userService = new UserService(users: userList) expect: 'Invoke count() method' expectedCount == userService.count() where: expectedCount | userList 0 | null 0 | [] 1 | ['mrhaki'] 2 | ['mrhaki', 'hubert'] } }
So what happens here? We use a new label where
which contains a data table. Each row of the data table represent a new test run with the data from the row. In the setup
block we used an unbound variable userList
and in the expect
block the unbound variable expectedCount
. The variables get their values from the data table rows in the where
block. So the first run the UserService
instances gets null
assigned to the users
property and we expect the value 0 to be returned by the count()
method. In the second run we pass an empty list and expect also 0 from the count()
method. We have four rows, so our test is run four times when we invoke $ gradle test
.
We can make the fact that four tests are run explicit by using the @Unroll
annotation. We can use a String as argument describing the specific variable values used in a run. If we use the #
followed by the unbound variable name will it be replaced when we run the code:
package com.mrhaki.blog import spock.lang.Specification import spock.lang.Unroll class UserServiceSpecification extends Specification { @Unroll("Expect to count #expectedCount users for following list #userList") def "Return total number of users"() { setup: 'Create UserService instance with users' UserService userService = new UserService(users: userList) expect: 'Invoke count() method' expectedCount == userService.count() where: expectedCount | userList 0 | null 0 | [] 1 | ['mrhaki'] 2 | ['mrhaki', 'hubert'] } }
The generated XML with the test result contains the four runs with their specific names:
<?xml version="1.0" encoding="UTF-8"?> <testsuite errors="0" failures="0" hostname="ci-test" name="com.mrhaki.blog.UserServiceSpecification" tests="4" time="0.707" timestamp="2010-06-29T18:17:24"> <properties /> <testcase classname="com.mrhaki.blog.UserServiceSpecification" name="Expect to count 0 users for following list null" time="0.152" /> <testcase classname="com.mrhaki.blog.UserServiceSpecification" name="Expect to count 0 users for following list []" time="0.027" /> <testcase classname="com.mrhaki.blog.UserServiceSpecification" name="Expect to count 1 users for following list [mrhaki]" time="0.0050" /> <testcase classname="com.mrhaki.blog.UserServiceSpecification" name="Expect to count 2 users for following list [mrhaki, hubert]" time="0.0010" /> <system-out><![CDATA[]]></system-out> <system-err><![CDATA[]]></system-err> </testsuite>
This concludes the introduction to Spock testing. In the future we learn more about Spock and the great features it provide to make writing tests easy and fun.