There are two things that I want to point before we continue this lesson.

We don’t have to configure the category of our test cases. However, if we separate our unit, integration, and end-to-end tests by using JUnit 4 categories, we have to do this or otherwise our test cases won’t be run.

When I name my specification classes, I append the string: ‘Spec’ to the name of the system under specification (aka system under test).

We have just created our first Spock specification. Unfortunately our specification is useless because it doesn’t do anything. Before we can change that, we have to take a closer look at the structure of a Spock specification.

The Structure of a Spock Specification

A spock specification can have the following parts:

Constants are static fields that are useful when we want to name magic numbers.

Instance fields are a good place to store objects that belong to the specification’s fixture. An object belongs to the specification’s fixture if we use the object when we write our tests. Also, Spock recommends that we initialize our instance fields when we declare them.

Fixture methods are responsible for configuring the system under specification (SUS) before feature methods are invoked and cleaning up of the system under specification after feature methods have been invoked.

Feature methods specify the expected behavior of the system under specification.

Helper methods are methods that are used by the other methods found from the specification class.

The following code sample demonstrates the structure of a Spock specification class:

We can now identify the basic building blocks of a Spock specification. Let’s move on and take a closer look at constants and instance fields.

Adding Constants and Instance Fields to Our Specification

Before we can add constants and instance fields to our specification class, we have to understand that Spock Framework has two kinds of instance fields:

The objects stored in “normal” instance fields are not shared between feature methods. This means that every feature method gets its own object. We should prefer normal instance fields because they help us to isolate feature methods from each other.

The objects stored in “shared” instance fields are shared between feature methods. We should use shared fields if creating the object in question is expensive or we want to share something with all feature methods.

Enough with theory. Let’s add some constants and instance fields to our specification class. We can do this by following these steps:

Add a static constant called MESSAGE to our specification class and initialize it. This constant contains the expected message that should be returned by the getMessage() method of the MessageService class.

Add a normal instance field to our specification class and initialize it by creating a new MessageService object. Because we use a normal instance field, every feature method will get its own MessageService object.

Add a shared field to our specification class and initialize it by creating a new Object. When we create a shared field, we have to mark the field as shared by annotating it with the @Shared annotation. Also, because this is a shared field, we expect that every feature method will get the same object.

After we have added these fields to our specification class, its source code looks as follows:

Let’s demonstrate the difference between a normal and shared instance field by adding two feature methods to our specification class. These feature methods ensure that the getMessage() method of the MessageService class returns the expected message. However, the thing that interests us the most is that both feature methods write objects stored in the messageService and sharedObject fields to System.out.

After we have added these feature methods to our specification class, its source code looks as follows:

Even though we can now add fields to our specification class, we cannot write useful tests because we don’t know how we can configure or clean up the system under specification. It’s time to find out how we can use fixture methods.

Using Fixture Methods

Fixture methods are used to configure and clean up the system under specification and all fixture methods supported by the Spock Framework are optional.

Let’s add all fixture methods to our specification class. A Spock specification can have the following fixture methods:

The setupSpec() method is invoked before the first feature method is invoked. This method is typically used for performing resource intensive configuration that is shared by several feature methods.

The setup() method is invoked before every feature method. We can use this method for configuring the system under specification. Also, if our normal instance fields require setup code that is longer than one line, we should initialize them in this method.

The cleanup() method is invoked after every feature method. We can use this method for cleaning up the configuration that was done in the setup() method.

The cleanupSpec() method is invoked after all feature methods have been invoked. This is method used for cleaning up the configuration that was done in the setupSpec() method.

After we have added all fixture methods to our specification class, its source code looks as follows:

The setupSpec() and cleanupSpec() methods can use only shared instance fields.

Let’s add two feature methods to our specification class. These feature methods ensure that the getMessage() method of the MessageService class returns the expected message. Also, both feature methods write a String to System.out.

After we have written these feature methods, the source code of our specification class looks as follows:

When we run our specification, we notice that the following lines are written to System.out:

Before the first feature method
Before every feature method
First feature method
After every feature method
Before every feature method
Second feature method
After every feature method
After the last feature method

This output proves that the methods of our specification class are invoked in this order: