Writing Unit Tests With Spock Framework: Introduction to Specifications, Part Three

The previous part of this tutorial described the structure of a feature method and helped us to specify the expected behavior of the system under specification.

This time we will take a closer look at the where block that is used to provide input data for our feature methods when we are writing parameterized tests with Spock Framework.

Let's get started.

Additional Reading:

If you are not familiar with Spock Framework, you should read the following blog posts before you continue reading this blog post:

Writing Parameterized Tests With Spock Framework

Let's assume that we had to specify the expected behavior of the Math class' max(int a, int b) method. Its Javadoc states that it:

Returns the greater of two int values. That is, the result is the argument closer to the value of Integer.MAX_VALUE. If the arguments have the same value, the result is that same value.

The source code of our (a bit naive) specification class looks as follows:

import spock.lang.Specification

class MathSpec extends Specification {

    def "Get the max value of two numbers (without parameters)"() {

        expect: 'Should return the bigger number'
        Math.max(1, 0) == 1
        Math.max(2, 3) == 3
    }
}
Our feature method has only the expect block because we are specifying the expected behavior of a method that has no side effects.

Let's find out how we can rewrite this feature method by using data driven testing support of Spock Framework.

Rewriting the Expect Block

The first thing that we have to is to rewrite the expect block of our feature method. We can do this by following these steps:

First, we have to replace the hard coded int values with the "data variables" a, b, and c. These data variables are described in the following:

  • The a and b data variables are the method parameters which are passed to the max() method of the Math class.
  • The c data variable is the expected value that is returned by the Math class' max() method.

Second, we have to specify the expected behavior of the Math class' max() method by using these data variables.

The source code of our rewritten feature method looks as follows:

import spock.lang.Specification

class MathSpec extends Specification {

    def "Get the max value of two numbers"() {

        expect: 'Should return the bigger number'
        Math.max(a, b) == c
    }
}

After we have rewritten the expect block of our feature method to use data variables, we have to provide the input data for our feature method. Let's find out how we can provide the input data by using so called data pipes.

Providing Input Data by Using Data Pipes

We can provide the input data for our feature method by following these steps:

  1. Add a new where block into our feature method.
  2. Configure the values of each data variable by following these steps:
    1. Connect the data variable a with a data provider that contains the int values: 1 and 2.
    2. Connect the data variable b with a data provider that contains the int values: 0 and 3.
    3. Connect the data variable c with a data provider that contains the int values: 1 and 3.

The source code of our specification class looks as follows:

import spock.lang.Specification

class MathSpec extends Specification {

    def "Get the max value of two numbers"() {

        expect: 'Should return the bigger number'
        Math.max(a, b) == c

        where:
        a << [1,2]
        b << [0,3]
        c << [1,3]
    }
}

Although our where block is completely functional, it is not very readable. We can make it a lot more readable by using data tables.

Providing Input Data by Using Data Tables

We can create a data table by following these rules:

  • The first line of the data table declares the data variables.
  • The subsequent table rows are called data rows. These data rows contains the values of data variables that are passed to our feature method, and our feature method is invoked once per a data row.
  • The different column values of a table row are separated by using the pipe character ('|').

After we have replaced our old where block with a where block that provides the input data by using data tables, the source code of our specification class looks as follows:

import spock.lang.Specification

class MathSpec extends Specification {

    def "Get the max value of two numbers"() {

        expect: 'Should return the bigger number'
        Math.max(a, b) == c

        where:
        a | b | c
        1 | 0 | 1
        2 | 3 | 3
    }
}

Although our new where block is a lot cleaner than the where block which uses data pipes, we can make it a bit better by separating the input values and the expected output value(s) with a double pipe symbol ('||'). After we have done this, the source code of our specification class looks as follows:

import spock.lang.Specification

class MathSpec extends Specification {

    def "Get the max value of two numbers"() {

        expect: 'Should return the bigger number'
        Math.max(a, b) == c

        where:
        a | b || c
        1 | 0 || 1
        2 | 3 || 3
    }
}

Let's summarize what we have learned from this blog post.

Summary

This blog post has taught us three things:

  • We can specify the input values and the expected output values by using data variables.
  • We can provide input data for our feature methods by adding a where block into our feature method and using either data pipes or data tables.
  • Providing input data by using data tables is a lot cleaner than using data pipes.

The next part of this tutorial describes how we can create test doubles with Spock Framework.

P.S. You can get the example application of this blog post from Github.

7 comments… add one
  • MD Mahbubur Rahman Sep 5, 2016 @ 13:10

    In JUnit, multiple test cases can be executed in a loop with: @RunWith(Parameterized.class)
    How can this be achieved in Spock?

    I mean if you have multiple def for a single test, how you can loop with different sets of input data?
    Using where block in a def only iterates inside itself.

    • Petri Sep 5, 2016 @ 13:44

      Hi,

      Check out this blog post. It explains how you can write parameterized tests with Spock Framework (it covers both data pipes and data tables).

      • MD Mahbubur Rahman Sep 6, 2016 @ 6:02

        I know about data pipes and data tables. This concepts are for SINGLE "def" EXECUTION.
        I need to to about how to execute multiple defs as a unit test hence how to provide input data for that?

        • Petri Sep 6, 2016 @ 13:30

          Unfortunately I don't know if it is possible to create an input data set that is automatically used by multiple feature methods.

  • Kalyan Jan 6, 2017 @ 1:28

    Hi,
    You mentioned "The next part of this tutorial describes how we can create test doubles with Spock Framework." But I do not find any link to the next part four.

    • Petri Jan 12, 2017 @ 21:41

      Hi,

      There is no link to next part since I haven't written it :( I might continue writing this tutorial at some point if people think that this tutorial is valuable to them.

Leave a Reply