Core Java

Spock Tutorial for Beginners

1. Introduction

Spock is a testing framework for Java and Groovy applications. It’s very own Junit Runner, Sputnik, makes it compatible with most IDEs, build tools, and continuous integration servers.

2. Environment

This tutorial assumes that you have basic understanding of Gradle build framework and also that your Eclipse IDE (Luna) environment is fully setup and configured with

3. Learn By Example

To make this learning process a little interesting, let us do so using a basic example.
For this purpose I would get you started with creation of a Gradle project and configure the build.gradle before you start writing your Spock specifications.

3.1. Create and configure a Gradle project in Eclipse IDE

In the Eclipse IDE, click File -> New -> Other

Create Gradle Eclipse Project
Create Gradle Project

Select “Gradle Project”

Create Gradle Project
Create Gradle Project

Take a moment to read the suggestions in the following screen. Press next.

CP3_WM
Create Gradle Project – Welcome Page

Enter the name of your project.

CP4_WM
Create Gradle Project – Welcome Page

Keep the default and recommended Gradle Wrapper option selected and press next.

CP5_WM
Create Gradle Project – Gradle Wrapper

Press finish on the preview screen.

CP6_WM
Create Gradle Project – Preview

You have successfully created the Gradle project. The following is the project structure of your Gradle project.

Gradle Default Project Structure
Gradle Project Structure

The unit tests written using Junit are placed under folder src/test/java folder. The Groovy unit tests written using Spock framework are placed under src/test/groovy folder. Note that the default Gradle project structure do not have src/test/groovy folder. This will be created at later stage.

To enable your Gradle project to compile and run both Junit tests and Groovy tests include the following required dependencies in build.gradle.

 
dependencies {
             testCompile ("junit:junit:${junitVersion}")
             testCompile("org.codehaus.groovy:groovy-all:${groovyVersion}")
             testCompile("org.spockframework:spock-core:${spockFrameworkVersion}")
}

Create gradle.properties to configure dependencies versions as shown in the following few steps.

Right-mouse click on project SpoockyOne. Select New -> File as shown below.

create gradle.properties
create gradle.properties

Select the project root folder and enter the filename gradle.properties.

gradlePropertiesFile_2_WM
create gradle.properties

Configure the required dependencies versions in gradle.properties, as shown below.

gradle.properties

 
# dependencies versions
spockFrameworkVersion=1.0-groovy-2.4
groovyVersion=2.4.5
junitVersion=4.12
slf4jVersion=1.7.13
logbackCoreVersion=1.1.3
logbackClassicVersion=1.1.3

After including all the required dependencies, the complete build.gradle looks like this.

build.gradle

 
apply plugin: 'groovy'

version = "1.0"
description = "SpoockyOne"

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
	mavenLocal()
        jcenter()
}

sourceSets {
	main {
		java.srcDir "src/main/java"
	}
	test {
		java.srcDir "src/test/groovy"
	}
}

dependencies {
	compile("org.slf4j:slf4j-api:${slf4jVersion}")
	
	testRuntime("ch.qos.logback:logback-core:${logbackCoreVersion}")
	testRuntime("ch.qos.logback:logback-classic:${logbackClassicVersion}")

        testCompile ("junit:junit:${junitVersion}")
	testCompile("org.codehaus.groovy:groovy-all:${groovyVersion}")
	testCompile("org.spockframework:spock-core:${spockFrameworkVersion}")
}

Let us walk through the build.gradle.

 apply plugin: 'groovy'

The “groovy” plugin extends “java” plugin. This plugin supports joint compilation to allow the flexibility of combining Groovy and Java code with bi-directional dependency. A groovy class can extend a java class and vice versa.

 version = "1.0"
description = "SpoockyOne"

These are the project standard properties and are used in the build script to provide version and description of the project.

 sourceCompatibility = 1.8
targetCompatibility = 1.8

This build script specifies the source and target compatibility versions used for compiling Java sources.

sourceSets {
main {
java.srcDir "src/main/java"
}
test {
java.srcDir "src/test/groovy"
}
}

As you can see from the gradle project structure, src/test/groovy doesn’t exist by default. You have to create this folder src/test/groovy and add it to the sourceSets build script block in build.gradle. The above build script block specifies that the java source is under the folder src/main/java and the test scripts source is to be found under src/test/groovy.

 jcenter()

When jcenter() is included in build.gradle, Gradle looks for the configured libraries and their dependencies in JCenter Maven repository (https://jcenter.bintray.com).

 mavenLocal()

mavenLocal() can be specified to use local Maven cache as a repository.

 compile("org.slf4j:slf4j-api:${slf4jVersion}")
testRuntime("ch.qos.logback:logback-core:${logbackCoreVersion}")
testRuntime("ch.qos.logback:logback-classic:${logbackClassicVersion}")

To configure slf4j with logback as logging framework.

 testCompile("org.codehaus.groovy:groovy-all:${groovyVersion}")

In this case Groovy is only used for test code and thus the Groovy dependency should be added to the testCompile configuration.

 compile("org.codehaus.groovy:groovy-all:${groovyVersion}")

If Groovy is used for production code, the Groovy dependency should be added to the compile configuration.

 testCompile("org.spockframework:spock-core:${spockFrameworkVersion}")

This is to specify the Spock dependency version that needs to be downloaded from the repository.

Your gradle project is ready for build.

3.2. Build your project

Issue the following command at command prompt to build the project.

 gradlew clean build

The output of this command is shown below. This build process pulls the required libraries that are configured in build.gradle and resolves their dependencies.

gradlew clean build
gradlew clean build

As you can see in the following screen shot, the dependencies of the required libraries mentioned in build.gradle are also downloaded.

build_2_WM
gradlew clean build

For further reading about how these dependencies are resolved, follow the link Gradle dependency management.

3.3. Let’s get Groovy and Spocky

In the next few sections, we will create the following required java classes and interfaces.

  • Book.java
  • BookService.java
  • BookRepository.java
  • BookServiceImpl.java

BookServiceImpl, the implementation of BookService interface is the “System Under Specification” – the class which we are going to write specification for.

3.3.1. Create Book.java

Create a new java class Book.java as shown in the following screen shots.

Right-mouse click on the folder src/main/java. Select File -> New -> Other as shown below.

bookcreate_1WM
Create Book.java

Select “Class”

bookcreate_2_WM
Create Book.java

Specify the package name and java class name and click “Finish”.

Book.java
Book.java

Book.java

package com.readerscorner.model;

public class Book {

	Integer bookId;
	String title;
	String author;
	
	public Book(Integer bookId,  String title, String author) {
		super();
		this.bookId = bookId;
		this.title = title;
		this.author = author;
	}
	public Integer getBookId() {
		return bookId;
	}
	public void setBookId(Integer bookId) {
		this.bookId = bookId;
	}

	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getAuthor() {
		return author;
	}
	public void setAuthor(String author) {
		this.author = author;
	}
	@Override
	public String toString() {
		return "Book [bookId=" + bookId + ", title=" + title + ", author="
				+ author + "]";
	}
}

3.3.2. Create BookService.java

Create a java interface with name BookService.java as shown in the following screen shots.

Right-mouse click on the folder src/main/java. Select File -> New -> Other as shown below.

bookcreate_1WM
Create BookService.java

Select “Interface”

BookServiceCreate_1._WM
Create BookService.java

Specify the package name and the interface name as shown below.

BookServiceCreate_2_WM
Create BookService.java

BookService.java

package com.readerscorner.service;
import com.readerscorner.model.Book;
public interface BookService {
	Book retrieveBookDetails(Integer bookId);
}

3.3.3. Create BookRepository.java

Right-mouse click on the folder src/main/java. Select File -> New -> Other as shown below.

bookcreate_1WM
Create BookRepository.java

Select “Interface”

BookServiceCreate_1._WM
Create BookRepository.java

Specify the package name and the interface name as shown below.

BookRepositoryCreate_1_WM
Create BookRepository.java

BookRepository.java

package com.readerscorner.repository;

import com.readerscorner.model.Book;

public interface BookRepository {
	
	Book getBook(Integer bookId);

}

The project structure after creating the required classes is shown below.

ProjectStructure_2_WM
BookRepository.java

Please note that we haven’t yet written the BookServiceImpl.java which is the implementation for BookService.

3.3.4. Create BookServiceSpecification

Let us write the specification with name BookServiceSpecification to describe the expected features of our “System Under Specification”(SUS), BookServiceImpl, i.e, the implementation of BookService interface.

Spock specifications are written as groovy classes and placed under the folder src/test/groovy.

Create a groovy class BookServiceSpecification.groovy as shown in the following screens.

Create a folder src/test/groovy.

groovyfoldercreate_1_WM
Create src/test/groovy

Enter the folder name “groovy” and press “Finish”.

groovyfoldercreate_2_WM
Create src/test/groovy

Build the project once again using

gradlew clean build

Try to refresh the project in Eclipse IDE using “Refresh Gradle Project” as shown below.

refreshgradleproject_1_WM
Refresh Gradle Project

Right-mouse click on src/test/groovy folder. Select New->Other.

speccreate_1_WM
Create Specification

Select “Groovy Class”.

speccreate_2_WM
Create Specification

Specify the package name and Specification name as shown below.

speccreate_3_WM
Create Specification

Please note the warning displayed. “SpoockyOne is not a groovy project. Groovy Nature will be added to project upon completion.”

Please have a look at the project structure after creating the first Groovy class.

ProjectStructure_3_WM
Create Specification – Project Structure

3.3.5. Red -> Green -> Refactor

Let us follow the TDD cycle to progressively write and test our System Under Specification.

Step 1 – RED – Write a test that will fail unless that behaviour is present. In Step1, we will write the BookServiceSpecification.groovy without any BookServiceImpl implementation. When BookServiceSpecification is run, it will fail because of this reason.

Step 2/Step 3 – Green – Write just enough code to get the test to pass. In Step 2 and step 3, we will add basic implementation of BookServiceImpl and make the test BookServiceSpecification pass.

Step 4 – Refactor – As you have minimum code that works, refactor it further to achieve desired design.

3.3.5.1 Step 1

3.3.5.1.1 Write Specification

BookServiceSpecification.groovy

package com.readerscorner.test

import com.readerscorner.model.Book
import com.readerscorner.service.BookService;
import com.readerscorner.repository.BookRepository;

import spock.lang.Specification
import groovy.util.logging.Slf4j

@Slf4j
class BookServiceSpecification extends Specification {
	
	BookService bookService
	BookRepository bookRepository
	def setupSpec(){
		log.debug("setupSpec() - Runs once per Specification");
	}
	def setup(){
		log.debug ("setup() - Runs before every feature method");
		bookService = null;
		bookRepository = Stub(BookRepository);
		bookRepository.getBook(_) >> { int id -> 
			if (id == 1)
			{
				 Book b = new Book(1, 'Srujana', 'Spock Tut');
				 log.debug(b.toString());
				 return b;
			}
			 else if (id == 2)
			{
				Book b = new Book(2, 'Eugen', 'JUnit Tut');
				 log.debug(b.toString());
				return b;
			}
			else if (id == 3)
			{
				log.debug("Book with this ID does not exist");
				return null;
			}
		}
	}
	def "retrieved book object is not null"(){
		log.debug ("Feature method 1 - retrieved book object is not null- start");
		expect :
			bookService.retrieveBookDetails(id) != null
		where :
		id << [1, 2] 
	}	
	
	def "retrieved book object is null"(){
		log.debug ("Feature method - 2 retrieved book object is null - start");
		expect :
			bookService.retrieveBookDetails(id) == null
		where :
		
		id << 3
		
	}
	def cleanup(){
		log.debug ("Cleanup method - Runs  after every feature method.");
	}
	def cleanupSpec(){
		
		log.debug ("cleanupSpec() - Runs only once per specification");
	}
				
}
3.3.5.1.2 Run the test

There are two ways you can run this test.

  1. Use Eclise IDE: Select the BookServiceSpecification.groovy class. Right mouse click and Select Run as -> Junit Test.
     
    Run JUnit Test from Eclipse IDE
    Run JUnit Test from Eclipse IDE

    This test would fail with NullPointerException for obvious reasons. The bookService object is null and the specification is invoking retrieveBookDetails() method on this null object.
     

    JUnit Test Results
    JUnit Test Results
  2. At the command prompt use one of the following commands to run the test.
    gradlew clean test
    gradlew --stacktrace clean test

    As mentioned in above point 1, this test would FAIL. Following is the output of the test.
     

    Test Results From Command Prompt
    Test Results From Command Prompt

    As mentioned in the above command window screen shot, $PROJECT_HOME\build\reports\tests\index.html should display the test summary. Following is the screenshots of the test summary of the above failed test.
     

    Test Summary Report - Failed tests
    Test Summary Report – Failed tests

3.3.5.2 Step 2

3.3.5.2.1 Create/Refactor BookServiceImpl and BookServiceSpecification.

In this step we will make our specification pass by creating/refactoring BookServiceImpl class. Create new java class BookServiceImpl.java in Eclipse IDE as shown below.

Right-mouse click on the folder src/main/java. Select File -> New -> Other as shown below.

bookcreate_1WM
Create BookServiceImpl.java

Select “Class”

bookcreate_2_WM
Create BookServiceImpl.java

Specify the details as shown below click “Finish”.

BookServiceImpl_1_WM
Create BookServiceImpl.java

BookServiceImpl.java

BookServiceImpl.java
package com.readerscorner.impl;

import com.readerscorner.model.Book;
import com.readerscorner.repository.BookRepository;
import com.readerscorner.service.BookService;

public class BookServiceImpl implements BookService {
	
	BookRepository bookRepository;
	
	void setBookRepository(BookRepository bRepository){
		
		bookRepository = bRepository;
	}
	@Override
	public Book retrieveBookDetails(Integer bookId) {
		
		return bookRepository.getBook(bookId);
	}
}

And modify BookServiceSpecification.groovy as follows.

BookServiceSpecification.groovy

package com.readerscorner.test

import com.readerscorner.impl.BookServiceImpl;
import com.readerscorner.model.Book
import com.readerscorner.service.BookService;
import com.readerscorner.repository.BookRepository;

import spock.lang.Specification
import groovy.util.logging.Slf4j

@Slf4j
class BookServiceSpecification extends Specification {
	
	BookServiceImpl bookService
	BookRepository bookRepository
	def setupSpec(){
		log.debug("setupSpec() - Runs once per Specification");
	}
	
	def setup(){
		log.debug ("setup() - Runs before every feature method");
		bookService = new BookServiceImpl();
		bookRepository = Stub(BookRepository);
		bookService.setBookRepository(bookRepository)
		
		bookRepository.getBook(_) >> { int id -> 
			if (id == 1)
			{
				 Book b = new Book(1, 'Srujana', 'Spock Tut');
				 log.debug(b.toString());
				 return b;
			}
			 else if (id == 2)
			{
				Book b = new Book(2, 'Eugen', 'JUnit Tut');
				 log.debug(b.toString());
				return b;
			}
			else if (id == 3)
			{
				log.debug("Book with this ID does not exist");
				return null;
			}
		}
	}
	
	def "retrieved book object is not null"(){
		log.debug ("Feature method 1 - retrieved book object is not null- start");
		expect :
			bookService.retrieveBookDetails(id) != null
		where :
		id << [1, 2] 
	}	
	
	def "retrieved book object is null"(){
		log.debug ("Feature method - 2 retrieved book object is null - start");
		expect :
			bookService.retrieveBookDetails(id) == null
		where :
		id << 3
	}
	def cleanup(){
		log.debug ("Cleanup method - Runs  after every feature method.");
	}
	
	def cleanupSpec(){
		log.debug ("cleanupSpec() - Runs only once per specification");
	}
}
3.3.5.2.2 Run the test
  1. Build the project from command prompt using the following command:
    gradlew clean build
  2. Refresh the project – Right-mouse click on project root SpoockyOne – Select “Gradle” -> “Refresh Gradle Project”
     
    refreshgradleproject_1_WM
    Refresh Gradle Project
  3. In Eclipse IDE – Run as -> “JUnit Test”.
     
    JUnit Test Result - PASS
    JUnit Test Result – PASS
  4. From command prompt issue the following command:
    gradlew clean test

    The test summary report for the passed tests is shown below.
     

    Test Summary Report - PASS
    Test Summary Report – PASS

3.3.5.3 Step 3

3.3.5.3.1 Modify BookServiceSpecification

Modify BookServiceSpecification.groovy to include test for exception handling.

BookServiceSpecification.groovy

package com.readerscorner.test

import com.readerscorner.impl.BookServiceImpl;
import com.readerscorner.model.Book
import com.readerscorner.service.BookService;
import com.readerscorner.repository.BookRepository;
import spock.lang.Specification
import groovy.util.logging.Slf4j

@Slf4j
class BookServiceSpecification extends Specification {

	BookServiceImpl bookService
	BookRepository bookRepository
	def setupSpec(){
		log.debug("setupSpec() - Runs once per Specification");
	}
	def setup(){
		log.debug ("setup() - Runs before every feature method");

		bookService = new BookServiceImpl();
		bookRepository = Stub(BookRepository);
		bookService.setBookRepository(bookRepository)

		bookRepository.getBook(_) >> { int id ->
			if (id == 1) {
				Book b = new Book(1, 'Srujana', 'Spock Tut');
				log.debug(b.toString());
				return b;
			}
			else if (id == 2) {
				Book b = new Book(2, 'Eugen', 'JUnit Tut');
				log.debug(b.toString());
				return b;
			}
			else if (id == 3) {
				log.debug("Book with this ID does not exist");
				return null;
			}
			else if (id <= 0) {
				throw new IllegalArgumentException("Invalid Book ID");
			}
		}
	}
	def "retrieved book object is not null"(){
		log.debug ("Feature method 1 - retrieved book object is not null- start");
		expect :
		    bookService.retrieveBookDetails(id) != null
		where :
		    id << [1, 2]
        }

	def "retrieved book object is null"(){
		log.debug ("Feature method - 2 retrieved book object is null - start");
		expect :
		  bookService.retrieveBookDetails(id) == null
		where :
		  id << 3
	}
	def "book id must be greater than 0"(){
		log.debug ("Feature method 3 - book id must be greater than 0 - start");
		given :
 	                       //NA
		when:
		   bookService.retrieveBookDetails(-3) 
		then:
		   thrown(IllegalArgumentException)
	}
	def cleanup(){
		log.debug ("Cleanup method - Runs after every feature method.");
	}
	def cleanupSpec(){
		log.debug ("cleanupSpec() - Runs only once per specification");
	}
}
3.3.5.3.2 Run the test
  1. Build the project from command prompt using the following command:
    gradlew clean build
  2. Refresh the project – Right-mouse click on project root SpoockyOne – Select “Gradle” -> “Refresh Gradle Project”
     
    refreshgradleproject_1_WM
    Refresh Gradle Project
  3. In Eclipse IDE – Run as -> “JUnit Test”.
     
    JUnit Test Results
    JUnit Test Results
  4. From command prompt issue the following command:
    gradlew clean test

    The test summary report for the passed tests is shown below.
     

    testsummary_6_WM
    Test Summary Report

3.3.5.4. Step 4

To keep it simple and straight further refactoring of the project is omitted from here. In our example, in Step 4:

  • we could further refactor the project by including implementation for BookRepository interface
  • Modify the specification to further test the interaction between BookServiceImpl and BookRepository implementation.

This Step 4 could be a take-home for you as your homework.

3.3.5.5. Get to Know BookServiceSpecification

Let’s step through the BookServiceSpecification.groovy

import groovy.util.logging.Slf4j
@Slf4j

This annotation is to use logging in the specification. Build.gradle is needed to be configured to use slf4j and the logging framework of choice (logback in this example). Though in this example the logging is done to standard output(which is by default), you may configure it to log to a log file of choice.

import spock.lang.*

The spock.lang package has the required types needed for writing specifications

class BookServiceSpecification extends Specification { … }

As shown in our example, a specification is written as a Groovy class. The spock.lang.Specification is the base class for all Spock specifications. The spock.lang.Specification class not only has methods needed to write specification but also instructs JUnit to run tests using Spock’s native Junit runner, Sputink.

def setupSpec(){}
def setup(){}
def cleanup(){}
def cleanupSpec(){}

These methods are called “fixture methods”. These are used to setup and clean up the environment in which the feature methods are run. As shown in the example:

  • setupSpec()– runs once before the first feature method
  • cleanupSpec()– runs once after the first feature method
  • setup() – runs before every feature method
  • cleanup() – runs after every feature method.
def "retrieved book object is not null"(){}
def "retrieved book object is null"(){}
def "book id must be greater than 0"(){}

These are called “feature methods”. These describe the expected behavior of the system under specification. As shown in the example, choose the names wisely based on the behavior you are specifying.

Every feature method always has a “stimulus” block and a “response” block defined. For example, when block is used to provide “stimulus” to the feature method and then is used to provide the expected “response”.

when:
bookService.retrieveBookDetails(-3)
then:
thrown(IllegalArgumentException)

Yet another example is, expect block where the “stimulus” and “response” is mentioned in a single expression.

expect :
bookService.retrieveBookDetails(id) == null

As shown in our example the feature methods, retrieved book object is not null and retrieved book object is null can be referred as “data driven feature methods”. They both used where block which defines a set of data. The where block defines multiple versions of the feature method.

For example,

where :
id << [1, 2]

This where block creates two versions of the feature method, one where id=1 and another where id=2. Where block always comes last in the feature method and may not be repeated.

bookRepository = Stub(BookRepository);
bookRepository.getBook(_) >> { }

Here we are stubbing the getBook() method of BookRepository to make it respond to method calls in certain way. As you would have observed, BookRepository interface is never implemented so far. We are creating Stub of BookRepository and stubbing the behaviour of getBook() method.

We stubbed the method in such a way that it behaves different for different values of parameter id.

For detailed reading of other concepts such as fixture methods, feature methods, blocks and many other interesting features of Spock, please follow the docs at Spock Framework reference documentation

4. References

If you would like to view, edit, run, and even publish your Spock specifications without setting up your own workspace environment, use the link:

I would recommend you to follow the below links for additional reading.

5. Conclusion

We had created a basic Spock specification with few fixture and feature methods. You may continue developing your skills with further reading at the links provided in the above section. Please do not forget to do your homework. Enjoy!!

6. Download the Eclipse project

Download

You can download the full source code of this example here: Spock – Beginners Tutorial

Srujana Cherukuri

Srujana holds Master of Computer Applications degree from Osmania University, Hyberabad, India. She is currently working as a Java/J2EE Consultant and has experience of working with a number of clients in a variety of domains.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Patricia Hallam-Mark
Patricia Hallam-Mark
6 years ago

Thank you so much, Srujana for putting together this great tutorial. Until reading this, I had only modified other people’s Gradle/Groovy/Spock projects. I was struggling with creating a new one from scratch, and also struggling with finding an online resource that started at the beginning. This was exactly what I needed. I wish I had found it sooner!

Back to top button