Spock : A new era of functional testing

Lacks of Testing framework + Mocking framework == Spock

Spock is a testing framework that in great extent utilizes Groovy’s syntax making your tests comprehensible and easy on the eyes which means that runs on the Java Virtual Machine (JVM) and let you test code written in any of the languages the JVM supports.

Why you might use Spock?

Tests created in Spock tend to be more informative, better arranged and easier to understand for others.

  • Easy to learn. It really is, even if you don’t know Groovy.
  • Very Good syntactic sugar for mocking, stubbing and spying these operations can be accomplished by really simple code, hence they do not veil test’s logic.
  • Detailed information. This alone should make you leave behind any other frameworks.
  • Beautiful language. I’ve tried other BDD frameworks, but Spock has created a better and more expressive domain-specific language  (DSL).

Minimal Groovy knowledge you need for Spock

Groovy is a language that it’s compiled to bytecode that is executed by the JVM, and it’s very similar to Java, but with features comparable to Ruby, Python, or Javascript.

For example, everything is treated as an object and semicolons are optional:

int x = 4 // Valid  
x.getClass() // java.lang.Integer 

Simple test in Spock

def "should return 2 from first element of list"() {  
given:  List list = new ArrayList<>()  
when:  list.add(1)  
then:  2 == list.get(0) 
} 

Above test should fail (putting 1 into list and then expect 2 to be retrieved). Spock will display a clear and easy to understand error message, explaining precisely what went wrong.

Condition not satisfied: 2 == list.get(0)   
|         |         |      |  [1] 1   false 

Spock Blocks

Blocks divide a method into sections. They start with a label and extend either to the beginning of the next block or the end of the method. We already use the given, when and then, but there’re six blocks actually:

  • setup: (or its alias, given:)
  • when:
  • then:
  • expect:
  • cleanup:
  • where:

Please take notice how clear a declaration of a test in Spock is. Any one do not have to follow the standard naming convention for methods and instead can declare the name of a test between two apostrophes. Thanks to that creating long but descriptive names for tests seems more natural and as well allows others to better understand what the purpose of a test it.
As mentioned before tests in Spock are arranged in a way resembling BDD tests. Each test can be divided into three sections.

Setup (Given section)

First of all we want to specify the context within which we would like to test a functionality. This is where you want to specify the parameters of your system/component that affect a functionality that is under the test. This section tends to get really big for all Spock rookies, but ultimately you will learn how to make it more concise and descriptive.

When section

This is where we can specify what we want to test. In other words what interaction with tested object/component we would like to study.

Then section

Now, here we verify what the result of the action performed in when section was. I.e. what was returned by the method (black box test) or what interactions have taken place in mocks and spies used inside tested function (white box test).

def "should return false if user does not have role required for  viewing page"() {
given:  // context pageRequiresRole 
Role.ADMIN  userHasRole Role.USER 
when:  // some action is performed  
boolean authorized = authorizationService.isUserAuthorizedForPage(user, page)              
then:  // expect specific result  
authorized == false
} 

Alternative sections

In Spock documentation you will be able to find some alternatives for the above sections, e.g.  expect,cleanup,where.

Expect

The expect: block is the same as then:, it can only contain conditions or variable definitions, but it doesn’t require a then: block:

def "should calculate power of number 2"() {
  expect:  Math.pow(2) == 4 
} 

Cleanup

The optional cleanup: block is used to free resources and is run even if the code before the block has thrown an exception. It’s usually the last block in the method, but it may only be followed by a where: block:

given:  def file = new File('/temp/1.txt')  //...  
cleanup:  file.close()

Where

def 'data-driven test'() { 
expect:  x + y == z  
where:   x  | y  | z  
         3  | 4  | 7  
         19 | 23 | 42  
         71 | 12 | 83  
}

Spock will run as many tests as rows in the where block. In the example, Spock will execute three tests, and the vars x, y, and z will be initialized to the following values:

Fixture Methods

Fixture methods are responsible for setting up and cleaning up the environment in which feature methods are run. Usually it’s a good idea to use a fresh fixture for every feature method, which is what the setup() and cleanup()methods are for.

def setupSpec() {}    // runs once -  before the first feature method
def setup() {}        // runs before every feature method
def cleanup() {}      // runs after every feature method
def cleanupSpec() {}  // runs once -  after the last feature method

Stubbing method calls

In Spock we can distinguish three classes that are able to override a behaviour of some other class or interface: Stubs, Mocks and Spies. In this section we will focus on Stubs as unlike two others imitating other classes is their only responsibility. Nevertheless the syntax for imitating behaviour is the same for all three classes, hence everything shown here will work the same way with Mocks and Spies.

Creating Stub

In order to create a Stub one has to call the Stub() method inside a Spock test.

def "creating example stubs"() {  
given:  List list = Stub(List)  
        List list2 = Stub() 
        // preffered way  
        def list3 = Stub(List)  
}

Specifying the return value

Basically what we want to do with Stubs is to define what should happen when a particular method of stubbed class is invoked. In order to specify what value should be returned by stubbed method we use right shift operator >> and then specify the return value.

def "should return Role.USER when asked for role"() {  
given:  List list = Stub()  
list.size() >> 3  
expect:  // let's see if this works  
list.size() == 3 
}

Specifying side effects

If we want to specify some side effect as the result of method invocation we put a closure after right shift operator. Thus every time the method be will invoked during the execution of the test the code within the closure will be executed.

def "specifying side effects"() {  
given:  List list = Stub()  
list.size() >> { println "Size method has been invoked" } 
}

Throwing an exception as the result of method invocation

We can use the above side effect technique to throw an exception when a method is invoked.

def "specifying that exception should be thrown"() {  
given:  List list = Stub()  
list.size() >> { throw new IllegalStateException() } 
} 

Checking interactions on Mock/Spies

Sometimes you do not really need to specify the behaviour of a dummy object you create for your unit test. Rather than that you are interested in checking whether a particular method from some interface has been invoked during the execution of your programme. To achieve that you can use Mock or Spy. In this section we will discuss the former of two, but everything presented here can be used with Spies as well.

Using mocks has one drawback though that we have to be aware of. When we check interactions, rather than verifying a contract of some interface, we are testing its particular implementation. Nevertheless, tests checking interactions on components can be really handy in case we carry out a large refactoring and e.g. we want to make sure that as a result we will not loose a crucial validation before inserting data to database.

Creating Mock

In order to create a Mock one has to call Mock() method inside a Spock test.

def "creating example stubs"() {  
given:  List list = Mock(List)  
List list2 = Mock() 
// preffered way  
def list3 = Mock(List)  
}

Matching invocations in mocks

The same means that were used to match invocation for stubbing can be used with mocks.

def "should fail due to wrong user"() { 
given:  UserService userService = Mock()  
User user = new User(name: 'Mefisto')  
when:  userService.save(user)  
then:  1 * userService.save({ User u -> u.name == 'Lucas' }) } 
Too few invocations for: 1 * userService.save({ User u -> u.name == 'Lucas' }) (0 invocations) 
Unmatched invocations (ordered by similarity): 1 * userService.save(ExampleSpockTest$User(Mefisto)) 

There are lots of other features present in Spock, just explore them.