Creating Mocks With Spock Framework

After we have finished this lesson, we:

  • Can create mocks with Spock Framework.
  • Understand how we can verify the interactions that happened between the system under specification and a mock.
  • Know how we can stub the methods of a mock object.

Creating Mocks With Spock Framework

We can create mocks with Spock Framework by using the Mock() method of the MockingApi class. This method takes the type of the created mock object as a method parameter and returns the created mock object.

When we create a new mock with Spock Framework, we must store the created mock in a normal instance field. This ensures that every feature method gets its own mock object. In other words, the configuration done in a feature method doesn’t affect other feature methods.

Let’s create a MessageRepository mock and use this mock when we create a new MessageService object. After we have configured the system under specification, the source code of our specification class looks as follows:

1import org.junit.experimental.categories.Category
2import spock.lang.Specification
3
4@Category(UnitTest.class)
5class MessageServiceSpec extends Specification {
6
7    def repository = Mock(MessageRepository)
8    def service = new MessageService(repository)
9}

There are three things that I want to point out before we can continue this lesson:

  • Because the system under specification was described in the previous lessons of this topic, I won’t repeat myself here.
  • If the system under specification (aka MessageService) requires setup code that is longer than one line, we should configure it in the setup() method because (IMO) it makes our specification class a bit cleaner.
  • We can also mock classes with Spock Framework. If we want to mock classes, we have to add either Byte-Buddy 1.6.4 (or newer) or cglib-nodep 3.2 (or newer) to the classpath.

Additional Reading:

After we have configured the system under specification, we are finally ready to use our new mock. Next, we will find out how we can verify the interactions that happened between the system under specification and a mock.

Verifying Interactions

When we want to verify the interactions that happened between the system under specification and a mock, we have to follow these steps:

  1. Specify the number of expected method calls (aka interactions). This number can be a fixed number or a range.
  2. Identify the mock object whose method must be invoked.
  3. Identify the invoked method. We can identify the invoked method (aka expected interaction) by using the syntax that we normally use when we invoke a method.
  4. Specify the expected method parameters. We can specify the expected method parameters by using so called argument constraints .

Let’s take a look at seven examples that demonstrate how we can verify the interactions that happened between the system under specification and a mock.

A Method Was Not Invoked

Let’s ensure that the findById() method of the MessageRepository mock was not invoked when we created a new message. After we have specified the number of expected interactions, the source code of our specification class looks as follows:

 1import org.junit.experimental.categories.Category
 2import spock.lang.Specification
 3
 4@Category(UnitTest.class)
 5class MessageServiceSpec extends Specification {
 6
 7    def repository = Mock(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Verify that the findById() method was not invoked'() {
11
12        given: 'We want to create a new message'
13        def message = new Message()
14        message.messageText = 'Hello World!'
15
16        when: 'We create a new message'
17        service.create(message)
18
19        then: 'Should not find a message'
20        0 * repository.findById(_)
21    }
22}

A Method Was Invoked Once

Let’s verify that the save() method of the MessageRepository mock was invoked once when we created a new message. After we have specified the number of expected interactions, the source code of our specification class looks as follows:

 1import org.junit.experimental.categories.Category
 2import spock.lang.Specification
 3
 4@Category(UnitTest.class)
 5class MessageServiceSpec extends Specification {
 6
 7    def repository = Mock(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Verify that the save() method was invoked once'() {
11
12        given: 'We want to create a new message'
13        def message = new Message()
14        message.messageText = 'Hello World!'
15
16        when: 'We create a new message'
17        service.create(message)
18
19        then: 'Should save the message'
20        1 * repository.save(message)
21    }
22}
To be more specific, we ensure that the save() method was invoked once and that the Message object given as a method parameter is equal to the Message object which is created in the setup block of our feature method.

A Method Was Invoked Between X and Y Times

If we want to ensure that a method of a mock object was invoked between X and Y times, we have to specify the number of expected interactions by using the following format: (X..Y).

Let’s ensure that the save() method of the MessageRepository mock was invoked between zero and two times when we created a new message. After we have specified the number of expected interactions, the source code of our specification class looks as follows:

 1import org.junit.experimental.categories.Category
 2import spock.lang.Specification
 3
 4@Category(UnitTest.class)
 5class MessageServiceSpec extends Specification {
 6
 7    def repository = Mock(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Verify that the save() method was invoked between 0 and 2 times'() {
11
12        given: 'We want to create a new message'
13        def message = new Message()
14        message.messageText = 'Hello World!'
15
16        when: 'We create a new message'
17        service.create(message)
18
19        then: 'Should save the message'
20        (0..2) * repository.save(message)
21    }
22}

A Method Was Invoked at Least X Times

If we want to verify that a method of a mock object was invoked at least X times, we have to specify the number of expected interactions by using the following format: (X.._).

Let’s verify that the save() method of the MessageRepository mock was invoked at least one time when we created a new message. After we have specified the number of expected interactions, the source code of our specification class looks as follows:

 1import org.junit.experimental.categories.Category
 2import spock.lang.Specification
 3
 4@Category(UnitTest.class)
 5class MessageServiceSpec extends Specification {
 6
 7    def repository = Mock(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Verify that the save() method was invoked at least one time'() {
11
12        given: 'We want to create a new message'
13        def message = new Message()
14        message.messageText = 'Hello World!'
15
16        when: 'We create a new message'
17        service.create(message)
18
19        then: 'Should save the message'
20        (1.._) * repository.save(message)
21    }
22}

A Method Was Invoked at Most X Times

If we want to ensure that a method of a mock object was invoked at most X times, we have to specify the number of expected interactions by using the following format: (_..X).

Let’s verify that the save() method of the MessageRepository mock was invoked at most one time when we created a new message. After we have specified the number of expected interactions, the source code of our specification class looks as follows:

 1import org.junit.experimental.categories.Category
 2import spock.lang.Specification
 3
 4@Category(UnitTest.class)
 5class MessageServiceSpec extends Specification {
 6
 7    def repository = Mock(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Verify that the save() method was invoked at most one time'() {
11
12        given: 'We want to create a new message'
13        def message = new Message()
14        message.messageText = 'Hello World!'
15
16        when: 'We create a new message'
17        service.create(message)
18
19        then: 'Should save the message'
20        (_..1) * repository.save(message)
21    }
22}

A Method Was Invoked Any Number of Times (Including Zero Times)

If we want to verify that a method was invoked any number of times, we have to specify the number of expected interactions by using the underscore character ( _ ).

Let’s verify that the save() method of the MessageRepository mock was invoked any number of times when we created a new message. After we have specified the number of expected interactions, the source code of our specification class looks as follows:

 1import org.junit.experimental.categories.Category
 2import spock.lang.Specification
 3
 4@Category(UnitTest.class)
 5class MessageServiceSpec extends Specification {
 6
 7    def repository = Mock(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Verify that the save() method was invoked any number of times'() {
11
12        given: 'We want to create a new message'
13        def message = new Message()
14        message.messageText = 'Hello World!'
15
16        when: 'We create a new message'
17        service.create(message)
18
19        then: 'Should save the message'
20        _ * repository.save(message)
21    }
22}
If we specify the number of expected interactions by using this approach, our test will pass even if the save() method is not invoked.

Using Argument Constraints

When we specify the expected interactions, we have to specify the expected method parameters. We can do this by using argument constraints. Because argument constraints were described in the earlier lesson of this topic , I won’t repeat myself here.

However, we will take a look at one example that demonstrates how we can use argument constraints when we specify the expected interactions. Let’s ensure that save() method of the MessageRepository mock was invoked once when we created a new message and that the Message object given as a method parameter has the correct message text.

After we have specified the number of expected interactions, the source code of our specification class looks as follows:

 1import org.junit.experimental.categories.Category
 2import spock.lang.Specification
 3
 4@Category(UnitTest.class)
 5class MessageServiceSpec extends Specification {
 6
 7    def repository = Mock(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Verify that a message was saved with the correct message text'() {
11
12        given: 'We want to create a new message'
13        def message = new Message()
14        message.messageText = 'Hello World!'
15
16        when: 'We create a new message'
17        service.create(message)
18
19        then: 'Should save a message that has the correct message text'
20        1 * repository.save({ it.messageText == 'Hello World!' })
21    }
22}

We can now verify the interactions that happened between the system under specification and a mock object. Next, we will find out how we can stub the methods of a mock object.

Stubbing Method Behavior

We can also stub the methods of a mock object. When we do this, we have to know these two things:

  • All mock objects created with Spock Framework allow unexpected invocations. The return value of such invocation is the default value of the method’s return type such as zero, false, or null. Also, it’s important to understand that mocks and stubs use a bit different logic when they select the return values of unexpected invocations.
  • When we want to mock and stub the same interaction (aka method invocation), we have to use one interaction. This means that we have to verify the expected interaction and configure the response of that interaction at the same time.

Because the previous lesson of this topic described how we can configure the response of a stubbed method, I won’t repeat myself here. However, we will take a look at one example that demonstrates how we can verify the expected interaction and configure the response of that interaction.

Our job is to write a feature method that specifies the behavior of the create() method of the MessageService class. Our feature method is almost ready, but we still have to verify the expected interaction and configure the response of that interaction. We can finish our feature method by following these steps:

  1. Ensure that the save() method of the MessageRepository mock was invoked once when we created a new message.
  2. Implement a closure that sets the id of the saved Message object and returns the saved object.

After we have verified the expected interaction and configured the response of that interaction, the source code of our specification class looks as follows:

 1import org.junit.experimental.categories.Category
 2import spock.lang.Specification
 3
 4@Category(UnitTest.class)
 5class MessageServiceSpec extends Specification {
 6
 7    def repository = Mock(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Verify that a message was saved and return the method parameter'() {
11
12        given: 'We want to create a new message'
13        def message = new Message()
14        message.messageText = 'Hello World!'
15
16        when: 'We create a new message'
17        def returned = service.create(message)
18
19        then: 'Should save the message and return the saved message'
20        1 * repository.save(message) >> { Message saved ->
21            saved.id = 1L
22            return saved
23        }
24
25        and: 'Should return the found message with correct id'
26        returned.id == 1L
27
28        and: 'Should return the found message with correct message text'
29        returned.messageText == 'Hello World!'
30    }
31}

We can now create mocks with Spock Framework, configure the expected interactions, and configure the response of a stubbed method. Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us five things:

  • We can create a mock by using the Mock() method of the MockingApi class.
  • All mocks created with Spock Framework allow unexpected invocations.
  • We should store our mocks in normal instance fields because this ensures that the configuration done in a feature method cannot affect other feature methods.
  • When we configure the number of expected interactions, we can use either a fixed number or a range.
  • If we want to mock and stub the same method invocation, we have to verify the expected interaction and configure the response of that interaction at the same time.

Get the source code from Github