Introduction to Feature Methods
After we have finished this lesson, we:
- Can identify the building blocks of a feature method.
- Can use these building blocks when we write feature methods.
This lesson assumes that:
Watch the Lesson
The text version of this lesson is given in the following:
The Structure of a Feature Method
A feature method describes the expected behavior of a feature that is implemented by the system under specification. We can name our feature methods by using String
literals, and this is a huge advantage over JUnit 4 (or TestNG) because we can (and we should) use sentences that actually make sense.
The source code of a specification class that has one empty feature method looks as follows:
import org.junit.experimental.categories.Category import spock.lang.Specification @Category(UnitTest.class) class ExampleSpec extends Specification { def 'A feature method'() { } }
Every feature method consists of so-called blocks. Each block has a label, and the body of the block extends to the beginning of the next block or to the end of the feature method. A feature method might have the following blocks:
- The setup block must be the first block of a feature method and it contains the configuration of the described feature. A feature method can have only one setup block.
- The when and then block describes the stimulus (when) and the expected response (then). A feature method can have multiple when and then blocks.
- The expect block describes the stimulus and the expected response in a single expression. A feature method can have only one expect block, and it is possible to add both when and then and expect blocks to the same feature method. However, this is not very practical.
- The cleanup block is used to clean up the resources used by a feature method, and it is invoked even if the feature method throws an exception. A feature method can have only one cleanup block.
- The where block must be the last block of a feature method, and it is used to write data-driven feature methods. A feature method can have only one where block.
This lesson helps you to use setup, when and then, expect, and cleanup blocks. We will talk more about the where block in the next lesson of this topic.
The following figure illustrates the structure of a feature method:

In other words, the structure of a feature method looks as follows:
import org.junit.experimental.categories.Category import spock.lang.Specification @Category(UnitTest.class) class ExampleSpec extends Specification { def 'A feature method'() { //setup block //when and then blocks //expect block //cleanup block //where block } }
Additional Reading:
We can now identify the blocks of a feature method. Let’s move on and find out how we can use these blocks when we write feature methods.
Writing Feature Methods
During this lesson we will learn to use these blocks when we create a feature method which specifies the expected behavior of the get()
method of the HashMap
class. Let’s start by finding out how we can use the setup block.
Using the Setup Block
As we remember, the setup block is the first block of a feature method and we can use it for configuring the described feature. The setup block has an optional label (setup
or given
) and we can create an implicit setup block by omitting that label.
Let’s create an implicit setup block that puts one value to the specified HashMap
object. After we have created this setup block, the source code of our specification class looks as follows:
@Category(UnitTest.class) class HashMapSpec extends Specification{ def map = new HashMap() def 'Get value from a map'() { def key = 'key' def value = 1 map.put(key, value) } }
If we think that that the implicit setup block is not clear enough, we can start our setup block by using the label: setup
. After we have added this label to our setup block, the source code of our specification class looks as follows:
@Category(UnitTest.class) class HashMapSpec extends Specification{ def map = new HashMap() def 'Get value from a map'() { setup: def key = 'key' def value = 1 map.put(key, value) } }
If we want to write our feature methods by using the given-when-then format, we can replace the setup
label with the label: given
and describe our setup block by using a String
literal.
After we have modified our setup block, the source code of our specification class looks as follows:
@Category(UnitTest.class) class HashMapSpec extends Specification{ def map = new HashMap() def 'Get value from a map'() { given: 'Map contains one key-value pair' def key = 'key' def value = 1 map.put(key, value) } }
Additional Reading:
After we have configured the described feature, we have to specify the expected behavior. Let’s find out how we can do it by using when and then blocks.
Using When and Then Blocks
We can specify the expected behavior of the described feature by using when and then blocks which always occur together. The when block describes the stimulus and the then block describes the expected response.
When we want to use when and then blocks, we have to follow these steps:
First, we have to create a when block by following these rules:
- A when block must start with the label:
when
. - A when block can have an optional description that is given by using a
String
literal. - A when block can contain any code.
Second, we have to create a then block by following these rules:
- A then block must be placed right after the when block that describes the stimulus.
- A then block must start with the label:
then
. - A then block can have an optional description that is given by using a
String
literal. - A then block can contain only variable definitions, conditions, exception conditions and interactions.
Let’s create a when and then block which verifies that the get()
method of the HashMap
class returns the found value. After we have created this block, the source code of our specification class looks as follows:
@Category(UnitTest.class) class HashMapWhenThenSpec extends Specification{ def map = new HashMap() def 'Get value from a map'() { given: 'Map contains one key-value pair' def key = 'key' def value = 1 map.put(key, value) when: 'A value is found with the given key' def found = map.get(key) then: 'Should return the found value' found == value } }
As we remember, a feature method can have multiple when and then blocks. Let’s create another when and then block which verifies that the get()
method of the HashMap
class returns null
if no value is found with the given key.
After we have created this block, the source code of our specification class looks as follows:
@Category(UnitTest.class) class HashMapWhenThenSpec extends Specification{ def map = new HashMap() def 'Get value from a map'() { given: 'Map contains one key-value pair' def key = 'key' def value = 1 map.put(key, value) when: 'A value is found with the given key' def found = map.get(key) then: 'Should return the found value' found == value when: 'A value is not found with the given key' def incorrectKey = 'incorrectKey' found = map.get(incorrectKey) then: 'Should return null' found == null } }
Additional Reading:
When we take a closer look at our feature method, we notice that the when block seems a bit artificial. Let’s clean up our feature method by using the expect block.
Using the Expect Block
An expect block describes the stimulus and the expected response in a single expression. We can create an expect block by following these rules:
- An expect block must start with the label:
expect
. - An expect block can have an optional description that is given by using a
String
literal. - An expect block can contain only conditions and variable definitions.
Let’s create an expect block which verifies that the get()
method of the HashMap
class returns the found value. After we have created this block, the source code of our specification class looks as follows:
@Category(UnitTest.class) class HashMapExpectSpec extends Specification{ def map = new HashMap() def 'Get value from a map'() { given: 'Map contains one key-value pair' def key = 'key' def value = 1 map.put(key, value) expect: 'Should return the found value' map.get(key) == value } }
Our next step is to verify that the get()
method of the HashMap
class returns null
when no value is found with the given key. Because a feature method can have only one expect block, we can “divide” that block into several parts by using the and
label. The and
label is used to describe individual parts of a block, and it has an optional description that is given by using a String
literal.
After we have added a new part to the expect block, the source code of our specification class looks as follows:
@Category(UnitTest.class) class HashMapExpectSpec extends Specification{ def map = new HashMap() def 'Get value from a map'() { given: 'Map contains one key-value pair' def key = 'key' def value = 1 map.put(key, value) expect: 'Should return the found value' map.get(key) == value and: 'Should return null when value is not found' def incorrectKey = 'incorrectKey' map.get(incorrectKey) == null } }
It should be clear that we can specify the expected behavior by using either a when and then or an expect block. The question is: how can we choose the right tool for the job?
The Spock documentation helps us to find an answer to this question:
As a guideline, use when-then to describe methods with side effects, and expect to describe purely functional methods.
Additional Reading:
We can now specify the expected behavior of the described feature by using both when and then and expect blocks. This is a good start, but sometimes our feature method reserves resources that must be freed afterwards.
Let’s find out how we can clean up these resources by using the cleanup block.
Using the Cleanup Block
A cleanup block is used to free the resources used by a feature method, and Spock guarantees that it is invoked even if the feature method throws an exception. We can create a cleanup block by following these rules:
- A cleanup block must start with the label:
cleanup
. - A cleanup block must be placed after the when and then and/or expect blocks.
Let’s assume that we have created a feature method which creates a new file. Naturally we want to delete the created file after the feature method has been finished successfully or after an exception has been thrown.
We can do this by adding the following cleanup block to our feature method:
@Category(UnitTest.class) class FileSpec extends Specification { def 'Create a new file'() { setup: def file = new File("/tmp/foo.txt") when: 'A new file is created' file.createNewFile() then: 'Should create a new file' file.exists() file.isFile() !file.isDirectory() cleanup: file?.delete() } }
Because the cleanup block is invoked even if the feature method throws an exception, we must write our cleanup block by using defensive programming because we cannot know which line throws the exception.
Additional Reading:
We can now identify the blocks of a feature method, and we know how we can use these blocks when we write feature methods.
Let’s summarize what we learned from this lesson.
Summary
This lesson has taught us six things:
- A feature method consists of blocks.
- The setup block configures the described feature.
- The when and then block describes the stimulus and the expected response.
- The expect block describes the stimulus and the expected response in a single expression.
- The cleanup block is used to clean up the resources used by a feature method.
- We can describe individual parts of a block by using the
and
label.
Previous LessonNext Lesson