Search

September 10, 2019

Spocklight: Use Stub or Mock For Spring Component Using @SpringBean

When we write tests or specifications using Spock for our Spring Boot application, we might want to replace some Spring components with a stub or mock version. With the stub or mock version we can write expected outcomes and behaviour in our specifications. Since Spock 1.2 and the Spock Spring extension we can use the @SpringBean annotation to replace a Spring component with a stub or mock version. (This is quite similar as the @MockBean for Mockito mocks that is supported by Spring Boot). We only have to declare a variable in our specification of the type of the Spring component we want to replace. We directly use the Stub() or Mock() methods to create the stub or mock version when we define the variable. From now on we can describe expected output values or behaviour just like any Spock stub or mock implementation.

To use the @SpringBean annotation we must add a dependency on spock-spring module to our build system. For example if we use Gradle we use the following configuration:

...
dependencies {
    ...
    testImplementation("org.spockframework:spock-spring:1.3-groovy-2.5")
    ...
}
...

Let's write a very simple Spring Boot application and use the @SpringBean annotation to create a stubbed component. First we write a simple interface with a method that accepts an argument of type String and return a new String value:

package mrhaki.spock;

public interface MessageComponent {
    String hello(final String name);
}

Next we use this interface in a Spring REST controller where we use constructor dependency injection to inject the correct implementation of the MessageComponent interface into the controller:

package mrhaki.spock;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MessageController {

    private final MessageComponent messageComponent;

    public MessageController(final MessageComponent messageComponent) {
        this.messageComponent = messageComponent;
    }

    @GetMapping(path = "/message", produces = MediaType.TEXT_PLAIN_VALUE)
    public String message(@RequestParam final String name) {
        return messageComponent.hello(name);
    }

}

To test the controller we write a new Spock specification. We use Spring's MockMvc support to test our controller, but the most important part in the specification is the declaration of the variable messageComponent with the annotation @SpringBean. Inside the method where we invoke /message?name=mrhaki we use the stub to declare our expected output:

package mrhaki.spock

import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc
import spock.lang.Specification

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get

@WebMvcTest(MessageController)
class MessageControllerSpec extends Specification {

    @Autowired
    private MockMvc mockMvc

    /**
     * Instead of the real MessageComponent implementation
     * in the application context we want to use our own
     * Spock Stub implementation to have control over the
     * output of the message method.
     */
    @SpringBean
    private MessageComponent messageComponent = Stub()

    void "GET /message?name=mrhaki should return result of MessageComponent using mrhaki as argument"() {
        given: 'Stub returns value if invoked with argument "mrhaki"'
        messageComponent.hello("mrhaki") >> "Hi mrhaki"

        when: 'Get response for /message?name=mrhaki'
        final response = mockMvc.perform(get("/message").param("name", "mrhaki"))
                                .andReturn()
                                .getResponse()

        then:
        response.contentAsString == "Hi mrhaki"
    }
}

Written with Spock 1.3-groovy-2.5 and Spring Boot 2.1.8.RELEASE.