Providing Data

Assign Multiple Data Variables from Provider

We can write data driven tests with Spock. We can specify for example a data table or data pipes in a where: block. If we use a data pipe we can specify a data provider that will return the values that are used on each iteration. If our data provider returns multiple results for each row we can assign them immediatelly to multiple variables. We must use the syntax [var1, var2, var3] << providerImpl to assign values to the data variables var1, var2 and var3. We know from Groovy the multiple assignment syntax with parenthesis ((var1, var2, var3)), but with Spock we use square brackets.

In the following sample specification we have a simple feature method. The where: block shows how we can assign the values from the provider to multiple data variables. Notice we can skip values from the provider by using a _ to ignore the value.

package com.mrhaki.spock

@Grab('org.spockframework:spock-core:0.7-groovy-2.0')
import spock.lang.*

class MultiDataVarSpec extends Specification {

    @Unroll("#value as upper case is #expected")
    def "check upper case value of String"() {
        expect:
        value.toUpperCase() == expected

        where:
        // Multi data variables value and expected,
        // will be filled with elements from a row
        // on each iteration. The first element of each
        // row is ignored.
        // E.g. on first iteration:
        // value = 'abc'
        // and expected = 'ABC'
        [_, value, expected] << [
            [1, 'abc', 'ABC'],
            [2, 'def', 'DEF'], 
            [3, '123', '123']
        ]
    }


}

Code written with Spock 0.7-groovy-2.0 and Groovy 2.3.3.

Original post written on June 25, 2014

Write Our Own Data Provider

We can use data pipes to write data driven tests in Spock. A data pipe (<<) is fed by a data provider. We can use Collection objects as data provider, but also String objects and any class that implements the Iterable interface. We can write our own data provider class if we implement the Iterable interface.

In the following sample code we want to test the female property of the User class. We have the class MultilineProvider that implements the Iterable interface. The provider class accepts a multiline String value and returns the tokenized result of each line in each iteration.

package com.mrhaki.spock

@Grab('org.spockframework:spock-core:0.7-groovy-2.0')
import spock.lang.*

class ProviderSampleSpec extends Specification {

    @Unroll("Gender #gender for #name is #description")
    def "check if user is female or male based on gender value"() {
        given:
        def userData = '''\
            1;mrhaki;M;false
            2;Britt;F;true'''

        expect:
        new User(name: name, gender: gender).female == Boolean.valueOf(expected)

        where:
        [_, name, gender, expected] << new MultilineProvider(source: userData)

        // Extra data variable to be used in
        // @Unroll description.
        description = expected ? 'female' : 'not female'
    }

}

/**
 * Class under test. 
 */
class User {
    String name, gender

    Boolean isFemale() {
        gender == 'F'
    }
}

/**
 * Class implements Iterable interface so 
 * it can be used as data provider.
 */
class MultilineProvider implements Iterable {
    def source
    def lines
    def separator = ';'

    private int counter

    /**
     * Set multiline String as source 
     * and transform to a List of String
     * values and assign to the lines
     * property.
     */
    void setSource(source) {
        this.source = source.stripIndent()
        lines = this.source.readLines()
    }

    @Override
    Iterator iterator() {
        [
            hasNext: { 
                counter < lines.size() 
            },
            next: { 
                lines[counter++].tokenize(separator)
            }
        ] as Iterator
    }
}

Code written with Spock 0.7-groovy-2 and Groovy 2.3.3.

Original post written on June 25, 2014

Extra Data Variables for Unroll Description

Spock's unroll feature is very powerful. The provider data variables can be used in the method description of our specification features with placeholders. For each iteration the placeholders are replaced with correct values. This way we get a nice output where we immediately can see the values that were used to run the code. Placeholders are denoted by a hash sign (#) followed by the variable name. We can even invoke no-argument methods on the variable values or access properties. For example if we have a String value we could get the upper case value with #variableName.toUpperCase(). If we want to use more complex expressions we must introduce a new data variable in the where block. The value of the variable will be determined for each test invocation and we can use the result as a value in the method description.

package com.mrhaki.spock

import spock.lang.*

class SampleSpec extends Specification {

    @Unroll
    def "check if '#value' is lower case"() {
        expect:
        value.every { (it as char).isLowerCase() } == result

        where:
        value || result
        'A'   || false
        'Ab'  || false
        'aB'  || false
        'a'   || true
        'ab'  || true
    }

}

If we look at the output of the tests we see the method names are not really representing the code we test. For example we can not see if the value was lower case or not.

We rewrite the specification and add a new data variable unrollDescription in the where block. We then refer to this variable in our method name description.

package com.mrhaki.spock

import spock.lang.*

class SampleSpec extends Specification {

    @Unroll
    // Alternatively syntax as 
    // unroll annotation argument:
    // @Unroll("'#value' is #unrollDescription")
    def "'#value' is #unrollDescription"() {
        expect:
        value.every { (it as char).isLowerCase() } == result

        where:
        value || result
        'A'   || false
        'Ab'  || false
        'aB'  || false
        'a'   || true
        'ab'  || true

        unrollDescription = result ? 'lower case' : 'not lower case'
    }

}

When we look at the output we now have more descriptive method names:

This post is inspired by the great Idiomatic Spock talk by Rob Fletcher at Gr8Conf 2014 Europe.

Code written with Spock 0.7 for Groovy 2.

Original post written on June 16, 2014

Reuse Variables In Data Providers

Writing a parameterized specification in Spock is easy. We need to add the where: block and use data providers to specify different values. For each set of values from the data providers our specifications is run, so we can test for example very effectively multiple input arguments for a method and the expected outcome. A data provider can be anything that implements the Iterable interface. Spock also adds support for a data table. In the data table we define columns for each variable and in the rows values for each variable. Since Spock 1.1 we can reuse the value of the variables inside the data provider or data table. The value of the variable can only be reused in variables that are defined after the variable we want to reuse is defined.

In the following example we have two feature methods, one uses a data provider and one a data table. The variable sentence is defined after the variable search, so we can use the search variable value in the definition of the sentence variable.

package mrhaki

import spock.lang.Specification
import spock.lang.Unroll

class CountOccurrencesSpec extends Specification {

    @Unroll('#sentence should have #count occurrences of #search (using data table)')
    void 'count occurrences of text using data table'() {
        expect:
        sentence.count(search) == count

        where:
        search  | sentence                                                  || count
        'ABC'   | "A simple $search"                                        || 1
        'Spock' | "Don't confuse $search framework, with the other $search" || 2
    }

    @Unroll('#sentence should have #count occurrences of #search (using data provider)')
    void 'count occurrences of text using data provider'() {
        expect:
        sentence.count(search) == count

        where:
        search << ['ABC', 'Spock']
        sentence << ["A simple $search", "Don't confuse $search framework, with the other $search"]
        count << [1, 2]
    }
}

When we run the specification the feature methods will pass and in we see in the @Unroll descriptions that the sentence variable uses the value of search:

Written with Spock 1.1-groovy-2.4.

Original post written on October 2, 2017