Creating Stubs With Spock Framework - Configuring the Response

After we have finished this lesson, we:

  • Can return the same object or value every time when a stubbed method is invoked.
  • Know how we can determine the returned object or value by using the method parameter of the stubbed method.
  • Understand how we can modify the method parameter of the stubbed method before we return it.
  • Know how we can ensure that the stubbed method throws an exception.

Introduction to System Under Specification

During this lesson, we will use a MessageRepository stub when we specify the expected behavior of the MessageService class. Because these components were described in the previous lesson of this topic , I won’ repeat myself here.

However, we had to make some changes to these components. These changes are described in the following:

First, we added a save() method to the MessageRepository interface. This method saves the information of the Message object given as a method parameter and returns the saved Message object.

The source code of the MessageRepository interface looks as follows:

1import java.util.Optional;
2
3interface MessageRepository {
4
5    Optional<Message> findById(Long id);
6
7    Message save(Message message);
8}

Second, we added a create() method to the MessageService class. This method gets the created Message object as a method parameter, saves the created object, and returns the saved object.

The source code of the MessageService class looks as follows:

 1public class MessageService {
 2
 3    private final MessageRepository repository;
 4
 5    public MessageService(MessageRepository repository) {
 6        this.repository = repository;
 7    }
 8
 9    public Message findById(Long id) {
10        return repository.findById(id).orElseThrow(
11                () -> new NotFoundException(String.format(
12                        "No message found with id: %d",
13                        id
14                ))
15        );
16    }
17
18    public Message create(Message message) {
19        return repository.save(message);
20    }
21}

Next, we will find out how we can configure the response of a stubbed method.

Configuring the Response of a Stubbed Method

The previous lesson of this topic described how we can create stubs with Spock Framework, identify the stubbed method, and configure the expected method parameters. These are all useful skills, but we cannot really use our stubs because we don’t know how we can configure the response of a stubbed method.

Let’s move on and take a look at six examples that demonstrate how we can configure the response of a stubbed method.

Returning the Same Object (or Value) on Every Invocation

When we want to return the same object or value every time when the stubbed method is invoked, we have to configure the returned object or value by using the right-shift operator (>>).

Let’s ensure that the findById() method of the MessageRepository stub returns the same Optional object every time when it is invoked by using the method parameter 1L.

After we have configured the MessageRepository stub, the relevant part 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 = Stub(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Return the same message on every invocation'() {
11
12        given: 'A message is found with the id 1'
13        def found = new Message()
14        repository.findById(1L) >> Optional.of(found)
15
16        when: 'We find a message with the id 1'
17        def returned = service.findById(1L)
18
19        then: 'Should return the found message'
20        returned == found
21    }
22}

Returning Different Objects (or Values) on Successive Invocations

When we we want to return different objects or values on successive invocations, we have to configure the returned objects or values by using the triple-right-shift operator (>>>).

Let’s ensure that the findById() method of the MessageRepository stub returns different Optional objects when it is invoked for the first and second time by using the method parameter 1L.

After we have configured the MessageRepository stub, the relevant part 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 = Stub(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Return different message objects on successive invocations'() {
11
12        given: 'Different messages are found on successive invocations'
13        def first = new Message()
14        def second = new Message()
15        repository.findById(1L) >>> [Optional.of(first), Optional.of(second)]
16
17        when: 'We find two objects by using the id 1'
18        def firstReturned = service.findById(1L)
19        def secondReturned = service.findById(1L)
20
21        then: 'Should return different objects on successive invocations'
22        firstReturned == first
23        secondReturned == second
24    }
25}

As we can see, when the findById() method is invoked for the first time, it returns an Optional object that the contains the first Message object. When the the findById() method is invoked for the second time, it returns an Optional object that contains the second Message object.

I don’t really use this feature because I think that it doesn’t help me to write deterministic tests. However, I decided to include this feature in this lesson because I wanted you to know that it’s possible to return different objects on successive invocations.

Determining the Returned Object (or Value) by Using the Method Parameter

If we want to determine the returned object or value by using the method parameter of the stubbed method, we have to configure the returned object or value by using the right-shift operator (>>) together with a closure.

Let’s ensure that the findById() method of the MessageRepository stub returns an Optional object that contains the found message when it is invoked by using the method parameter 1L. Also, we have to ensure that the findById() method returns an empty Optional when it is invoked by using a method parameter that is not equal to 1L.

We can do this by using one of these two options:

First, if the closure declares a single untyped parameter, that parameter contains the argument list of the stubbed method. Because the findById() method has only one method parameter, the id of the requested message is the first item of the argument list. Let’s implement the logic that selects the returned object by using this information.

After we have configured the MessageRepository stub, the relevant part of our specification class looks as follows:

 1import org.junit.experimental.categories.Categoryu
 2import spock.lang.Specification
 3
 4@Category(UnitTest.class)
 5class MessageServiceSpec extends Specification {
 6
 7    def repository = Stub(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Determine the returned message by using the method parameter'() {
11
12        given: 'A message is found with the id 1'
13        def found = new Message()
14        repository.findById(_) >> {args ->
15            args[0] == 1L ? Optional.of(found) : Optional.empty()
16        }
17
18        when: 'We find a message with the id 1'
19        def returned = service.findById(1L)
20
21        then: 'Should return the found message'
22        returned == found
23    }
24}

Second, if the closure declares more than one parameter or a single typed parameter, the method parameters of the stubbed method will be mapped one-by-one to the closure’s parameters. Let’s implement the logic that selects the returned object by using this information.

After we have configured the MessageRepository stub, the relevant part 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 = Stub(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Determine the returned message by using the method parameter'() {
11
12        given: 'A message is found with the id 1'
13        def found = new Message()
14        repository.findById(_) >> {Long id ->
15            id == 1L ? Optional.of(found) : Optional.empty()
16        }
17
18        when: 'We find a message with the id 1'
19        def returned = service.findById(1L)
20
21        then: 'Should return the found message'
22        returned == found
23    }
24}
I think that we should use the second option because it’s a lot cleaner than the first option.

Configuring the Returned Object (or Value) by Using a Closure

If we want to create the returned object or value inside a closure, we have to use the right-shift operator (>>) together with a closure that creates and configures the returned object or value.

Let’s ensure that the findById() method of the MessageRepository stub returns an Optional object that contains the found message when it is invoked by using the method parameter 1L.

After we have configured the MessageRepository stub, the relevant part 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 = Stub(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Configure the returned message by using a closure'() {
11
12        given: 'A message is found with the id 1'
13        repository.findById(1L) >> {
14            Optional.of(new Message(id: 1L, messageText: 'Hello World!'))
15        }
16
17        when: 'We find a message with the id 1'
18        def returned = service.findById(1L)
19
20        then: 'Should return the found message with correct id'
21        returned.id == 1L
22
23        and: 'Should return the found message with correct message text'
24        returned.messageText == 'Hello World!'
25    }
26}

Modifying the Method Parameter of the Stubbed Method Before Returning It

Sometimes we want to modify the method parameter of the stubbed method before we return it. For example, if we want to stub a method that saves the information of an object to the used database, we might want to set the value of the object’s primary key field before we return the saved object.

If we want to modify the method parameter of the stubbed method before we return it, we have to use the right-shift operator (») together with a closure that modifies the method parameter and returns it. When we create the closure, we must declare one typed parameter that contains the method parameter of the stubbed method.

Let’s ensure that the save() method of the MessageRepository stub sets the id of the saved Message object and returns the saved object.

After we have configured the MessageRepository stub, the relevant part 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 = Stub(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Modify the method parameter and return it'() {
11
12        given: 'We want to create a new message'
13        def message = new Message()
14        message.messageText = 'Hello World!'
15
16        and: 'An id is assigned to the saved message before it is returned'
17        repository.save(message) >> { Message saved ->
18            saved.id = 1L
19            return saved
20        }
21
22        when: 'We create a new message'
23        def returned = service.create(message)
24
25        then: 'Should return the saved message with correct id'
26        returned.id == 1L
27
28        and: 'Should return the saved message with correct message text'
29        returned.messageText == 'Hello World!'
30    }
31}

Throwing an Exception

If we want ensure that the stubbed method throws an exception, we have to configure the thrown exception using the right-shift operator (>>) together with a closure that throws the exception.

Let’s ensure that the findById() method of the MessageRepository stub throws a NotFoundException when it is invoked by using the method parameter 1L.

After we have configured the MessageRepository stub, the relevant part 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 = Stub(MessageRepository)
 8    def service = new MessageService(repository)
 9
10    def 'Throw an exception'() {
11
12        given: 'No message is found with the id 1'
13        repository.findById(1L) >> {
14            throw new NotFoundException('not found')
15        }
16
17        when: 'We find a message by using the id 1'
18        service.findById(1L)
19
20        then: 'Should throw exception'
21        thrown NotFoundException
22    }
23}

This method cannot throw an exception. If no message is not found, this method returns an empty Optional.

I use this example here only because I wanted to demonstrate how we can throw an exception. That being said, we must not break the API contract of our API when we stub method behavior!

We can know configure the response of a stubbed method. Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us four things:

  • We can return the same object or value on every invocation by using the right-shift operator (>>).
  • If we want to determine the returned object by using the method parameter of the stubbed method, we have to use the right-shift operator (>>) together with a closure that specifies the returned object or value.
  • If we want to modify the method parameter of the stubbed method before we return it, we have to use the right-shift operator (>>) together with a closure that modifies the method parameter and returns it.
  • If we want to ensure that the stubbed method throws an exception, we have to use the right-shift operator (>>) together with a closure that throws the exception.

Get the source code from Github