Test With Spring Course
Save Time by Writing Less Test Code
  • Home
  • Pre-Sales FAQ
  • Support
  • Log In

/ July 2, 2017 / petrikainulainen

Introduction to Spock Specifications

Lesson Progress:
← Back to Topic

After we have finished this lesson, we

  • Know how we can create specification classes.
  • Are familiar with the structure of a specification class.
  • Understand how we should use different fields.
  • Can use fixture methods.

This lesson assumes that:

  • You are familiar with JUnit 4 categories
  • You can run unit tests which use Spock Framework with Maven or Gradle

Watch the Lesson

 

The text version of this lesson is given in the following:

Creating Our First Spock Specification

The class that contains our test cases is called a specification class, and we can create a Spock specification class by following these steps:

  1. Create a new Groovy class.
  2. Extend the Specification class.
  3. Configure the category of the test cases found from our specification class.

After we have created a simple specification class, the source code of the created class looks as follows:

import org.junit.experimental.categories.Category
import spock.lang.Specification

@Category(UnitTest.class)
class ExampleSpec extends Specification {

}


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).

Additional Reading:

  • The API documentation of the Specification class

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:

import org.junit.experimental.categories.Category
import spock.lang.Specification

@Category(UnitTest.class)
class ExampleSpec extends Specification {
	//Constants
	//Fields
	//Fixture methods
	//Feature methods
	//Helper methods
}

Additional Reading:

  • Spock Primer: Specification

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:

  1. 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.
  2. 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.
  3. 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:

import org.junit.experimental.categories.Category
import spock.lang.Shared
import spock.lang.Specification

@Category(UnitTest.class)
class FieldExampleSpec extends Specification {

    static MESSAGE = 'Hello World!'
    def messageService = new MessageService()
    @Shared sharedObject = new Object()
}

Additional Reading:

  • The API documentation of the @Shared annotation

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:

import org.junit.experimental.categories.Category
import spock.lang.Shared
import spock.lang.Specification

@Category(UnitTest.class)
class FieldExampleSpec extends Specification {

    static MESSAGE = 'Hello World!'
    def messageService = new MessageService()
    @Shared sharedObject = new Object()

    def 'Get message one'() {
        println 'First feature method'
        println 'unique object: ' + messageService
        println 'shared object: ' + sharedObject

        expect: 'Should return the correct message'
        messageService.getMessage() == MESSAGE
    }

    def 'Get message two'() {
        println 'Second feature method'
        println 'unique object: ' + messageService
        println 'shared object: ' + sharedObject

        expect: 'Should return the correct message'
        messageService.getMessage() == MESSAGE
    }
}

Feature methods are described in the lesson of this topic. However, these feature methods are so simple that you should be able to understand what these feature methods are doing.

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

First feature method
unique object: com.testwithspring.master.MessageService@6b419da
shared object: java.lang.Object@60dcc9fe
Second feature method
unique object: com.testwithspring.master.MessageService@309e345f
shared object: java.lang.Object@60dcc9fe

In other words, we can see that:

  • The object that is stored in the normal instance field is not shared between feature methods.
  • The object that is stored in the shared instance field is shared between feature methods.

Additional Reading:

  • Spock Primer: Fields


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:

import org.junit.experimental.categories.Category
import spock.lang.Specification

@Category(UnitTest.class)
class FixtureMethodExampleSpec extends Specification {

    static MESSAGE = 'Hello World!'
    def messageService = new MessageService()

    def setupSpec() {
        println 'Before the first feature method'
    }

    def setup() {
        println 'Before every feature method'
    }

    def cleanup() {
        println 'After every feature method'
    }

    def cleanupSpec() {
        println 'After the last feature method'
    }
}

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:

import org.junit.experimental.categories.Category
import spock.lang.Specification

@Category(UnitTest.class)
class FixtureMethodExampleSpec extends Specification {

    static MESSAGE = 'Hello World!'
    def messageService = new MessageService()

    def setupSpec() {
        println 'Before the first feature method'
    }

    def setup() {
        println 'Before every feature method'
    }

    def cleanup() {
        println 'After every feature method'
    }

    def cleanupSpec() {
        println 'After the last feature method'
    }

    def 'Get message one'() {
        println 'First feature method'
        expect: 'Should return the correct message'
        messageService.getMessage() == MESSAGE
    }

    def 'Get message two'() {
        println 'Second feature method'
        expect: 'Should return the correct message'
        messageService.getMessage() == MESSAGE
    }
}

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:

  1. setupSpec()
  2. setup()
  3. First feature method
  4. cleanup()
  5. setup()
  6. Second feature method
  7. cleanup()
  8. cleanupSpec()

Additional Reading:

  • Spock Primer: Fixture Methods

Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us five things:

  • Every Spock specification class must extend the spock.lang.Specification class.
  • A Spock specification can have constants, instance fields, fixture methods, feature methods, and helper methods.
  • We should prefer normal instance fields because they help us to isolate feature methods from each other.
  • We should use shared instance fields only if creating the object in question is expensive or we want to share something with all feature methods.
  • We can initialize and clean up the system under specification by using fixture methods.

Get the source code from Github

← Previous Lesson Next Lesson →

Can I help you?

This is a free sample lesson of my Test With Spring course. If this lesson helped you to solve your problem, you should find out how my testing course can help you.

Support and Privacy

  • Pre-Sales FAQ
  • Support
  • Cookie Policy
  • No Bullshit Privacy Policy
  • No Bullshit Terms and Conditions

Test With Spring Course

  • Starter Package
  • Intermediate Package
  • Master Package

Free Sample Lessons

  • Introduction to JUnit 4
  • Introduction to Unit Testing
  • Introduction to Integration Testing
  • Introduction to End-to-End Testing
  • Introduction to Spock Framework
  • Introduction to Integration Testing – Spock Edition
  • Writing End-to-End Tests With Spock Framework

Copyright Koodikupla Oy 2016 — Built on Thesis by Themedy