Skip to content

Instantly share code, notes, and snippets.

@schultzisaiah
Created May 11, 2022 16:41
Show Gist options
  • Save schultzisaiah/09dd8ac30dd43bdbfa77fe8fbae1da95 to your computer and use it in GitHub Desktop.
Save schultzisaiah/09dd8ac30dd43bdbfa77fe8fbae1da95 to your computer and use it in GitHub Desktop.
Spock Testing: Overview and Cheat Sheet

Spock Testing

This is a copy/MD-conversion of the original article by Lukasz Janicki: Spock Testing – Spock tutorial – The Javatar . The webpage is no longer available, but this cheat-sheet is too good to loose! A simpler full demo of a Spock implementation can also be found here.

What is Spock?

Spock is a unit testing framework that in great extent utilizes Groovy’s syntax making your tests comprehensible and easy on the eyes. Although it is a Groovy technology you can use it to test your Java classes as well. What is the most important is that Spock makes writing tests fun. And I really mean it.

Why Spock?

I have to admit that even knowing all the benefits of TDD and tests over all I considered writing them as little pain in the neck. How has that changed when I started using Spock?

  1. Creating a test in Spock takes less time than using its standard equivalent (combination of JUnit and some mocking framework).
  2. Thanks to the syntactic sugar for mocking, stubbing and spying these operations can be accomplished by really simple code, hence they do not veil test’s logic.
  3. Spock enforces developers to arrange their tests in BDD like form, what makes your test even more clear.
  4. Thanks to Groovy’s syntax you can improve tests clarity even further using closures and straightforward map usage (see good practices at the end of this post).

Taking all into consideration I gather that tests created in Spock tend to be more informative, better arranged and easier to understand for other developers. Plus they simply look splendidly. What’s the most important is that Spock has turned testing into an extremely pleasant and rewarding experience.

Simple Test in Spock

Below you can find a simple test written in Spock.

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

Of course as the above test is broken (we are putting 1 into list and then expect 2 to be retrieved) test will fail. 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 

Test structure

Please take notice how clear a declaration of a test in Spock is. Developers 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.

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. setup, expect. The latter one is particularly interesting as it allows to create an extremely tiny tests.

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

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() } 
} 

Specifying different behaviour based on invocation order

We are able to define different behaviour of stubbed method based on the invocation order. In the below example a 1 will be returned as a result of first invocation of method size(), for the second invocation a 2 will be returned and for the third time and further a 3 will be returned.

def "should return different values"() { 
   given: 
      List list = Stub() 
      list.size() >>> [1, 2, 3] 
} 

We can even mix this notation with custom behaviour syntax.

def "should return different values or throw exception"() { 
   given: 
      List list = Stub() 
      list.size() >> { throw new IllegalStateException() } >>> [1, 2, 3] >> 
         { throw new IllegalStateException() } >>> [5, 6] 
} 

Conditional behaviour of stubbed method

We can specify various behaviours for stubbed method based on some conditions. Let’s say that in this case everytime a method updateRoleAndReturnPreviousOne() is invoked with Role.ADMIN parameter an exception should be thrown. Otherwise Role.USER should be returned as the result. Please note that this time, the method we stub takes a parameter, hence the underscore in method invocation: updateRoleAndReturnPreviousOne(_). Methods taking parameters will be covered in the next section, but for the time being let’s say that this notation can be basically translated into: whenever the updateRoleAndReturnPreviousOne() method is being invoked with one parameter: execute following action (closure after right shift operator) or return following value (value after right shift operator).

def "should act differently based on condition"() { 
    given: 
        User user = Stub() 
        user.updateRoleAndReturnPreviousOne(_) >> { Role role -> 
            if (Role.ADMIN == role) 
                throw new IllegalArgumentException() 
            else 
                return Role.USER 
        } 
} 

Stubbing methods that take parameters

Basic parameter matching

Let’s elaborate a little on the problem that was raised in the previous example. As you could see from the above examples in order to mimic a particular method we need to invoke it in the same way we would normally invoke this method in our code. So what should we do, if we would like to mimic a method that takes parameters? Does the ‘stubbing’ invocation have to pass parameters as well or should we omit their declaration? Let’s find out in the below example what happens if we choose the latter.

def "what will happen?"() { 
    given: 
        List list = Stub() 
        list.get() >> 0 
    expect: 
        list.get(0) == 0 
} 
Condition not satisfied: 

list.get(0) == 0 
|    |      | 
[]   |      false 
     java.lang.Object@12baa77e 

As you can see here, Spock was not able to match the invocation that happened inside expect section with the stubbing declaration we defined in the given section. On the other hand if we had specified that every time the get() method is invoked with parameter equal to 0, a 0 zero should be returned, the above example would have worked. This turns out to be quite handy, as we can explicitly specify what should be returned when a method is invoked with a particular parameter.

def "should return 2 for method parameter equal to 2"() { 
    given: 
        List list = Stub() 
        list.get(0) >> 0 
        list.get(1) >> { throw new IllegalArgumentException() } 
        list.get(2) >> 2 
    expect: 
        list.get(2) == 2 
} 

Matching complex objects passed as parameters

We can take it even further and bind a kind of validation of a parameter into the stubbing declaration. Let’s imagine we have a UserService interface with a method called save() that takes a User object as a parameter. User class has a String attribute called name.

class User { 
    String name 
} 

interface UserService { 
    void save(User user) 
} 

Let’s now specify that when save() method is called from UserService an exception is being thrown. To make it more sophisticated, let’s assume that this exception should be only thrown when the user with name Michael is being saved.

def "should throw exception if user's name is Michael otherwise no exception should be thrown"() { 
    given: 
        UserService service = Stub() 
        service.save({ User user -> 'Michael' == user.name }) >> { 
            throw new IllegalArgumentException("We don't want you here, Micheal!") 
        }

    when: 
        User user = new User(name: 'Michael') 
        service.save(user) 
    then: 
        thrown(IllegalArgumentException) 

    when: 
        User user2 = new User(name: 'Lucas') 
        service.save(user2) 
    then: 
        notThrown(IllegalArgumentException) 
} 

If you run this example you will find out that the stubbed behaviour was executed only for the case when user passed to the save method had indeed name Michael. The only limitation for the expression inside the closure is that it has to evaluate to a boolean value, other than that the sky is the limit.

Arguments matching wildcards

If you do not want to specify the explicit values for parameters in stubbed methods you can use wildcards. Spock provides following wildcards:

given: 
    // any argument 
    list.contains(_) >> true 

    // any argument of Integer type 
    list.add(_ as Integer) >> true 

    // any non null argument 
    list.add(!null) >> true 

    // any argument different than someObject 
    list.add(!someObject) >> true 
def "should throw exception if an Integer is added to the list"() { 
    given: 
        List list = Stub() 
        list.add(_ as Integer) >> { throw new IllegalArgumentException() }

    when: 
        list.add(2) 
    then: 
        thrown(IllegalArgumentException) 

    when: 
        list.add("String") 
    then: 
        notThrown(IllegalArgumentException) 
} 

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 mocks"() { 
   given: 
      List list = Mock(List) 

      List list2 = Mock() // preffered way 

      def list3 = Mock(List)       
} 

Checking interactions with Mock object

Now that we have created a Mock object, we can check what has happened with the object during the execution of code inside when section.

def "size method should be executed one time"() { 
    given: 
        List list 
    when: 
        list = Mock() 
    then: 
        1 * list.size() 
} 

We check interactions in then section. Likewise specifying custom behaviour for Stubs, to verify interactions with a Mock we need to call the method that we want to study. Prior to that we specify a cardinality of interactions that we expect that have happened. Here we assume that a method size() from mocked object has been invoked exactly one time during the execution of the test. Obviously this is not true in our case, thus Spock will inform us about the failure of our assertion.

Too few invocations for: 

1 * list.size()   (0 invocations) 

Unmatched invocations (ordered by similarity): 

None 

Matching invocations in mocks

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

then: 
    // method taking no parameter 
    1 * list.size() 

    // explicit parameter value 
    1 * list.add(1) 

    // wildcard as parameter 
    1 * list.add(!null) 

    // detailed check of parameter passed to function 
    1 * userService.save({User user -> user.name == 'Lucas'}) 
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)) 

Specifying a cardinality of an interaction

In the above examples we always checked whether a method has been invoked once, of course Spock allows us to do much more.

then: 
    // should not be invoked at all 
    0 * list.size() 

    // should be invoked at least one time 
    (1.._) * list.size() 

    // should be invoked at most one time 
    (_..1) * list.size() 

    // any number of calls 
    _ * list.size() 

Check the order of execution

What is more you can even specify the order by which interactions should take place. This is achieved by creating numerous then sections.

def "should first save object before committing transaction"() { 
    given: 
        UserService service = Mock() 
        Transaction transaction = Mock() 
    when: 
        service.save(new User()) 
        transaction.commit() 
    then: 
        1 * service.save(_ as User) 
    then: 
        1 * transaction.commit() 
} 

Now let’s see what happens if we switch statements in when section.

def "should first save object before committing transaction"() { 
    given: 
        UserService service = Mock() 
        Transaction transaction = Mock() 
    when: 
        transaction.commit() 
        service.save(new User()) 
    then: 
        1 * service.save(_ as User) 
    then: 
        1 * transaction.commit() 
} 
Wrong invocation order for: 

1 * service.save(_ as User)   (1 invocation) 

As expected Spock will discover that methods have been invoked in wrong order and will fail the test.

Spy in Spock

Unlike Stub or Mock a Spy is not exactly a dummy object. It’s fair to say that a Spy is rather a wrapper to a normal object. When needed it can override a behaviour of a particular method of some interface and like in Mock the interaction with Spy’s methods can be verified. We will discuss Spies in cooperation with UserService interface.

interface Transaction { } 

interface UserService { 
    boolean isServiceUp() 
    void save(User user) 
} 

class UserServiceImpl implements UserService { 

    UserServiceImpl(Transaction transaction) { } 

    @Override 
    boolean isServiceUp() { 
        return false 
    } 

    @Override 
    void save(User user) { 
        println "Saving user ${user.name} - UserServiceImpl" 
    } 
} 

Creating Spy

In order to create a Spy one has to call Spy() method inside a Spock test. As said before a Spy is a wrapper around an object. Having that in mind, image if we create a Spy using an interface and then some other component will try to invoke a method from the Spy. Unless, we define a custom behaviour for this method an error will be returned.

def "creating spy from interface is not a good idea"() { 
    given: 
        UserService service = Spy(UserService) 
    expect: 
        service.save(new User(name: 'Katherine')) 
} 
org.spockframework.mock.CannotInvokeRealMethodException: Cannot invoke real method on interface based mock object at org.spockframework.mock.runtime 

Rather than creating a Spy from interface, let’s create it the way it is meant to, that is using a class. As UserServiceImpl takes a Transaction argument, creating a Spy will have a different form from what we have been used to by Mocks and Stubs.

def "creating spy from class"() { 
    given: 
        Transaction transaction = Stub(Transaction) 
        UserService service = Spy(UserServiceImpl, constructorArgs: [transaction]) 
    expect: 
        service.save(new User(name: 'Katherine')) 
}

Verifying interactions in Spy

Like in Mocks you can verify what interactions have taken place with methods from spied class.

def "should call save method once and print text to console"() { 
    given: 
        Transaction transaction = Stub(Transaction) 
        UserService service = Spy(UserServiceImpl, constructorArgs: [transaction]) 
    when: 
        service.save(new User(name: 'Katherine')) 
    then: 
        1 * service(_) 
} 

Ok, but how is it different from what a Mock would do, you ask. The answer is really simple, as you can see we did not change the behaviour of the _save()_ method. Therefore if you open the console you will see that the message defined in the _UserServiceImpl_ has been be printed there.
"C:\Program Files... 
Saving user Katherine - UserServiceImpl 
  
Process finished with exit code 0 

Basically, whenever you want to verify that a method of some particular class has been invoked and you cannot use Mock as for some reason you want the original implementation of this method to be executed, use Spy instead of Mock.

Specifying behaviour in Spy

Like in Mocks and Stubs you can also specify what should be done when a particular method is invoked. Bear in mind that unlike two others Spy will override only a method that it has been explicitly told to override, the implementation of other methods will be unaffacted.

def "should override behaviour of one method only"() { 
    given: 
        Transaction transaction = Stub(Transaction) 
        UserService service = Spy(UserServiceImpl, constructorArgs: [transaction]) 
        service.isServiceUp() >> true 
    expect: 
        if (service.isServiceUp()) { 
            service.save(new User(name: 'Katherine')) 
        } 
} 
"C:\Program Files... 
Saving user Katherine - UserServiceImpl 

Process finished with exit code 0 

In the above example we have made sure that every time the isServiceUp() method is called it will return the true value. Nevertheless, the implementation of the other method has not changed, thus it has printed out a message to the console once invoked. Basically if you need to override an implementation of one or a number of methods in a class that you use in your test but at the same time want to leave the implementation of others method, use Spy in place of Stub.

Managing exceptions in tests

By far, you should have figured out from previous examples how to verify whether a certain type of exception has been thrown during the execution of a test. Along methods thrown() and notThrown() there is also a noExceptionThrown() method. If you wish to verify details of an exception that has been thrown you can assign a result of thrown() method to a variable and then verify the exception itself.

def "should throw IllegalArgumentException with proper message"() { 
    when: 
        throw new IllegalArgumentException("Does description matter?") 
    then: 
        def e = thrown(IllegalArgumentException) 
        e.message == "Does description matter?" 
} 

Extras and catches

There are few catches that you need to be aware of when creating tests in Spock.

Assingments created outside test methods

Assignments created at the level of class are evaluated before every test. This is particularly handy when you do not want to create the objectUnderTest in the body of each test. Instead you can do it at the level of class without the worry that the state of object will be shared between invocations of different tests.

private List objectUnderTest = [] 
  
def "test 1"() { 
    when: 
        objectUnderTest.add(1) 
    then: 
        objectUnderTest.size() == 1 
} 
  
def "test 2"() { 
    when: 
        objectUnderTest.add(1) 
    then: 
        objectUnderTest.size() == 1 
} 

If for some reason you would prefer to share the state of objects created at the level class you can annotate this particular variable with @Shared annotation.

@Shared 
private List list = [] 
  
def "test 1"() { 
    when: 
        list.add(1) 
    then: 
        list.size() == 1 
} 
  
def "test 2"() { 
    when: 
        list.add(1) 
    then: 
        list.size() == 2 
} 

Please note that Spock does not guarantee that tests specified in a class will be executed in the order they appear in the file. Hence, the above example could fail if Spock would run test 2 before test 1. To impose the execution of tests in the order in which they appear in class file we need to annotate a class with a @Stepwise annotation.

Verifying interaction of a method while changing its behaviour

Sometimes we want to both check whether a method has been invoked and change its behaviour. The way that feels the most natural here would be to specify the custom behaviour in a given section and verify if the method was actually invoked in a then section.

def "should return ten .. or not?"() { 
    given: 
        List list = Mock() 
        list.size() >> 10 
    when: 
        int sizeOfList = list.size() 
    then: 
        1 * list.size() 
        sizeOfList == 10 
} 
Condition not satisfied: 
  
sizeOfList == 10 
|          | 
0          false 

What happens here is that Spock does not allow overriding a behaviour of a method for the second time. When test is executed Spock will first search the then section for verification statements. As it finds one for the size() method, the expression list.size() >> 10 in the given section will be ignored as method size() has been already processed. In order to solve this problem we have to combine these two statements into one and put the result into a then section as this is the only place where verification statements can be placed.

def "now it works as expected"() { 
    given: 
        List list = Mock() 
    when: 
        int sizeOfList = list.size() 
    then: 
        1 * list.size() >> 10 
        sizeOfList == 10 
} 

Overriding previously specified behaviours

The very same rule applies for every case of overriding already specified behaviour of a method in Stub, Mock or Spy – it is not going to work. The following problem hits me every once in a while. I create a common Stub object for my tests at the class level and define there a behaviour of a particular method. By the time I create another test I forget that this particular Stub is already associated with some custom behaviours. When creating another test, at the method level I define a behaviour for the very same method that would fulfill my needs in this test. Then in the following 30 minutes I fail to realize why the stubbed object does not work as expected. No workaround this time, just be aware that this can happen.

List list = createListStub() 
  
List createListStub() { 
    List stub = Stub() 
    stub.size() >> 10 
    return stub 
} 
  
def "more than stub feels like a stab in the back"() { 
    given: 
        list.size() >> 20 
    expect: 
        list.size() == 20 
} 
Condition not satisfied: 
  
list.size() == 20 
|    |      | 
[]   10     false 

That keyword

Spock tries to enhance the way you write tests, providing you with means to make them more descriptive and as easy to read as stories. In line with this pursuit stands the keyword (static function that has be imported) that. It does not change the logic of your test, but gives you the possibility to modify an expect section.

def "lets make it even more descriptive"() { 
    given: 
        // some context 
    expect: 
        that userHasBeenLoggedOut() 
} 

Good practicies: Using descriptive methods inside test

I always strive to make as descriptive test as possible so that others or even future me will be able to understand what a test is about in the twinkling of an eye. To make it possible I try to reduce the size of given section as this one tends to get really big. What is more I aim to remove all the assingments from this section as well and wrap all stubbing declaration into nicely named methods. Let’s imagine we create a system that allows users to buy tickets for different type of shows: like gigs, movies or theater performances. We want to test whether BookingService responsible for booking tickets for a show specified by an user through a webpage will throw an exception if user performed one of forbidden actions. In this case try to cancel his tickets for a gig that takes place in two days time. Let’s say we allow users to cancel tickets for gigs at most three days before the show, otherwise only administrators can do that. Let’s take a look at test that does this job.

def "should throw exception if user tries to cancel tickets to a gig that starts in less than two days"() { 
    given: 
        User user = Stub() 
        user.getType() >> Role.USER 
        user.getName() >> "Name" 
        user.getId() >> 2L 
        user.getGroupIds() >> [2, 3, 4] 
  
        Action action = Stub() 
        action.date() >> "2000/12/10" 
        action.type() >> ActionType.CANCEL 
        action.source() >> ActionSource.WEBPAGE 
  
        Show show = Stub() 
        show.id() >> 2L 
        show.date() >> "2000/12/11" 
        show.type() >> ShowType.GIG 
  
        TicketOrder order = Stub() 
        order.id() >> 1 
        order.status() >> OrderStatus.PAID 
        order.amount() >> 2 
        order.show() >> show 
  
        BookingService objectUnderTest = new BookingService() 
    when: 
        objectUnderTest.book(user, order, action) 
    then: 
        thrown(IllegalActionException) 
} 

The first thing that can be surprising here is that we stub a lot of methods that do not feel to have a lot in common with our test, e.g. user.getId() or order.status(). The explanation for that is that a person who wrote a BookingService did not thought about dividing it into smaller components and now this class is responsible for too many things. The validation that is of our concern takes place within method conductValidation(), nevertheless before code from this method can be even executed, the validation from the preceding method conductPreValidation() has to be satisfied. Thus, we are forced to stub a number of methods not related to the logic of test – I reckon you have encountered this problem for a number of times.

class BookingService { 
    void book(User user, TicketOrder order, Action action) { 
        conductPreValidation(user, order, action) 
        conductValidation(user, order, action) // 

Let’s mark all the lines that have effect on the result of the test.

def "should throw exception if user tries to cancel tickets to a gig that starts in less than two days"() { 
    given: 
        User user = Stub() 
        user.getType() >> Role.USER 
        user.getName() >> "Name" 
        user.getId() >> 2L 
        user.getGroupIds() >> [2, 3, 4] 
  
        Action action = Stub() 
        action.date() >> "2000/12/10" 
        action.type() >> ActionType.CANCEL 
        action.source() >> ActionSource.WEBPAGE 
  
        Show show = Stub() 
        show.id() >> 2L 
        show.date() >> "2000/12/11" 
        show.type() >> ShowType.GIG 
  
        TicketOrder order = Stub() 
        order.id() >> 1 
        order.status() >> OrderStatus.PAID 
        order.amount() >> 2 
        order.show() >> show 
  
        BookingService objectUnderTest = new BookingService() 
    when: 
        objectUnderTest.book(user, order, action) 
    then: 
        thrown(IllegalActionException) 
} 

As you can see more than half of stubbed method does not affect the result of our tests. We will try to hide the implementation details from the test scenario and at the same time shrink the given section so that it no longer draws the whole attention of reader. To achieve that we will wrap stubbings into nicely named methods.

def "should throw exception if user tries to cancel tickets to a gig that starts in less than two days"() { 
    given: 
        User user = userWithRoleUser() 
        Action action = cancelActionOn10thOfDec2000() 
        Show show = showIsGigThatStartsOn11thOfDec2000() 
        TicketOrder order = ticketOrderForShow show 
        BookingService objectUnderTest = new BookingService() 
    when: 
        objectUnderTest.book(user, order, action) 
    then: 
        thrown(IllegalActionException) 
} 

I hope that you find it much more clear and less overwhelming. The given section is now easier to understand as it is shorter, exposes parameters that affect test result and at the same time hides ones that are necessary for code to run but have no impact on the tested logic. The one thing that causes my concern is the creation of TicketOrder which does not seem to have any impact on the tested logic, as it is needed only as a holder of Show object. We will remove it the next step.

Removing disruptive code

The next thing I would do with my tests is to remove as much noise from the code as possible. Let’s remove all assignments from the given section as they bring no value at all. I will also move the creation of object under test outside the body of test and create the TicketOrder object in the same method as Show object.

private User user 
private Action action 
private TicketOrder order 
  
private BookingService objectUnderTest = new BookingService() 
  
def "should throw exception if user tries to cancel tickets to a gig that starts in less than two days"() { 
    given: 
        userHasRoleUser() 
        cancelActionOn10thOfDec2000() 
        showIsGigThatStartsOn11thOfDec2000() 
    when: 
        objectUnderTest.book(user, order, action) 
    then: 
        thrown(IllegalActionException) 
} 

Improve method naming

Just to make our test even less scary let’s play a bit with methods’ names. For instance we will use parameters to avoid having long names without spaces as in the above example.

def "should throw exception if user tries to cancel tickets to a gig that starts in less than two days"() { 
    given: 
        userWithRole USER 
        cancelsTicketsOn "2000/12/10" 
        toGigThatTakesPlaceOn "2000/12/11" 
    when: 
        objectUnderTest.book(user, order, action) 
    then: 
        thrown(IllegalActionException) 
} 

Good practices: Using “and” label

Apart from section labels like given, when, then there is also a label and that can be used to split former labels into groups of related statements. In my current project I often come across classes which responsibility is to filter out invalid inputs.

class BookingValidator { 
    boolean isValidBooking(Booking booking) { 
        if (condition1(booking) && condition2(booking) 
                && condition3(booking) ... && conditionN(booking)) { 
            return true; 
        } else { 
            return false 
        } 
    } 
} 

Of course names of methods are more meaningful than the ones in the example, but I hope you get the general idea. In tests to cover all the possible cases I write a test for the happy case (all conditions are satisfied) and at least one negative test (when isValidTrade() returns false) per condition from the if statement. The happy case test would look like below.

private Booking booking = Stub() 
private BookingValidator objectUnderTest = new BookingValidator() 
  
def "should classify booking as valid if all conditions are satisfied [@see note on test name]"() { 
    given: 
        condition1IsSatisfied() // these names are meaningful, like for example: bookingIsDoneForAllowedAmountOfDays 
        condition2IsSatisfied() // bookingWillBePaidByCash() 
        condition3IsSatisfied() // bookingIsDoneFromWeb() 
        ...                     // and so on ... 
        conditionNIsSatisfied() // bookingIsDoneByRegisteredUser() 
    expect: 
        objectUnderTest .isValidBooking(booking) == true 
} 
  
private void condition1IsSatisfied() { 
    // whatever it takes to satisfy this conditions takes place here 
    booking.days() >> 2 
} 
  
/* 
private void condition2IsSatisfied() { 
  [...]   
*/ 

As I do not want to list all the conditions in a test name I try to come up with some business definition that would suggest others what is necessary in the particular scenario for positive validation. Now let’s create a test that should fail due to unsatisfied condition no. 2. Just to make sure that my tests will work even if someone rearrange conditions inside isValidBooking() method, in the test I will prepare the Booking object in the way that it satisfies all conditions, but the 2nd one.

def "should not classify booking as valid trusted client discount booking if it will be paid by credit card"() { 
    given: 
        bookingIsDoneForAllowedAmountOfDays() 
        bookingWillBePaidByCreditCard() 
        bookingIsDoneFromWeb() 
        ... 
        bookingIsDoneByRegisteredUser() 
    expect: 
        objectUnderTest.isValidBooking(booking) == false 
} 

As you can see the method bookingWillBePaidByCash() has changed into bookingWillBePaidByCreditCard(), also the name of test says that this will prevent the booking to be classified for a discount. To take this even a step further let’s separate the given section into two groups.

def "should not classify booking as valid trusted client discount booking if it will be paid by credit card"() { 
    given: "booking is eligible for discount due to" 
        bookingIsDoneForAllowedAmountOfDays() 
        bookingIsDoneFromWeb() 
        ... 
        bookingIsDoneByRegisteredUser() 
    and: "not satisfies discount requirements due to" 
        bookingWillBePaidByCreditCard() 
    expect: 
        objectUnderTest.isValidBooking(booking) == false 
} 

Now we have explicitly separated the given section into two sections that affect the object under test in opposite ways. Also we added descriptions to labels making it extremely clear which section affects object under test in which way.

Good practices: Using maps for method parameters

Let’s imagine we have a BookingService like the one below and we want to create a test that would check whether exception is throw if there is not enough tickets for a particular screening of a movie.

class BookingService { 
  
    private TicketService ticketService 
  
    void book(MovieBooking booking) { 
        int ticketsAvailable = ticketService.getAvailableTickets(booking.getMovie(), booking.getDate()) 
        if(ticketsAvailable < booking.getNumberOfTickets()) { 
            throw new NotEnoughTicketsException(); 
        } else { 
            // perform booking 
        } 
    } 
} 

We end up writing a test like the one below.

private MovieBooking booking = Stub() 
private TicketService ticketService = Stub() 
private BookingService objectUnderTest = new BookingService(ticketService: ticketService) 
  
def "should throw exception if there are not enough tickets for particular screening"() { 
    given: 
        booking.getMovie() >> "Rush" 
        booking.getDate() >> "10/10/2014 07:00PM" 
        booking.getNumberOfTickets() >> 2 
        ticketService.getAvailableTickets("Rush", "10/10/2014 07:00PM") >> 1 
    when: 
        objectUnderTest.book(booking) 
    then: 
        thrown(NotEnoughTicketsException) 
} 

This is not really bad, but imagine this functionality requires a lot more of stubbing etc. In that case we would like to extract related stubbing into meaningfully named methods.

   given: 
        booking "Rush",  "10/10/2014 07:00PM", 2 

This is not that bad as well, as parameters of this method somewhat differs from each other. There are cases though when this method could look like this:

given: 
        booking "Rush", "Peter Morgan", "Ron Howard", "10/10/2014 07:00PM", 2, 1 

In this case how can someone tell that the second parameter is the name of the screenwriter and the third one of the director. Can you still suspect that the number parameter is a number of tickets to be booked when there are two number parameters? In such cases I like to transform this type of method into more comprehensive form using maps.

def "should throw exception if there are not enough tickets for particular screening"() { 
    given: 
        bookingIsDoneFor movie: "Rush", tickets: 2, date: "10/10/2014 07:00PM" 
        availableTicketsFor movie: "Rush", on: "10/10/2014 07:00PM", equalTo: 1 
    when: 
        objectUnderTest.book(booking) 
    then: 
        thrown(NotEnoughTicketsException) 
} 
  
private bookingIsDoneFor(Map bookingDetails) { 
    booking.getMovie() >> bookingDetails['movie'] 
    booking.getDate() >> bookingDetails['date'] 
    booking.getNumberOfTickets() >> bookingDetails['tickets'] 
} 
  
private void availableTicketsFor(Map ticketDetails) { 
    ticketService.getAvailableTickets(ticketDetails['movie'], ticketDetails['on']) >> ticketDetails['equalTo'] 
} 

Parametrized tests

Spock also supports parametrized tests, in which you define a test structure once and then run the same test for different input values. Since I am going to post a separate article on this topic I will just provide you with some teaser for the time being. You can find more complex parametrized tests here.

def "this will run three tests"() { 
    expect: 
        Math.pow(base, 2) == expectedResult 
    where: 
        base || expectedResult 
        2    || 4 
        3    || 9 
        10   || 100               
} 

Setting up Spock

To start working with Spock you only need to add one additional dependency to your project. You can also play with Spock through the web console online application. One thing to remember though is that every Spock test has to extend class Specification.

// Gradle dependency:
testCompile group: 'org.spockframework', name: 'spock-core', version: '0.7-groovy-2.0' 

//Maven dependency:
org.spockframework 
spock-core 
0.7-groovy-2.0 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment