Loading...

June 29, 2010

Spocklight: Introduction to Spock Testing

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.