Spring is one of the most popular JVM-targeted frameworks. One of the reasons why it has become so popular is writing tests. Even before Spring Boot era, it was easy to run an embedded Spring application in tests. With Spring Boot, it became trivial. JUnit and Spock are two most popular frameworks for writing tests. They both provide great support and integration with Spring, but until recently it was not possible to leverage Spring’s @WebMvcTest in Spock. Why does it matter? @WebMvcTest is a type of an integration test that only starts a specified slice of Spring Application and thus its execution time is significantly lower compared to full end-to-end tests. Things have changed with Spock 1.2. Let me show you, how to leverage this new feature.

@WebMvcTest

It is easy to write great tests (clear and concise) for most of the components in a typical Spring Application. We create a unit test, stub interactions with dependencies and voila. Things are not so easy when it comes to REST controllers. Until Spring Boot 1.4 testing REST controllers (and all the ’magic’ done by Spring MVC) required running full application, which of course took a lot of time. Not only startup time was the issue. Typically, one was also forced to setup entire system’s state to test certain edge cases. This usually made tests less readable. @WebMvcTest is here to change that and now, supported in Spock.

@WebMvcTest with Spock

In order to use Spock’s support for @WebMvcTest, you have to add a dependency on Spock 1.2-SNAPSHOT, as GA version has not been released yet (https://github.com/spockframework/spock). For Gradle, add snapshot repository:

repositories {
    ...

    maven {
        url "https://oss.sonatype.org/content/repositories/snapshots/"
    }
}

and then the dependency:

dependencies {
    ...

    testCompile(
         ...

        "org.spockframework:spock-core:1.2-groovy-2.4-SNAPSHOT",
        "org.spockframework:spock-spring:1.2-groovy-2.4-SNAPSHOT"
    )
}

Sample application

I have created a fully functional application with examples. All snippets in this article are taken from it. The application can be found here: https://github.com/rafal-glowinski/mvctest-spock. It exposes a REST API for users to register to some event. Registration requirements are minimal: a user has to provide a valid email address, name, and last name. All fields are required.

Starting with Rest Controller (most imports omitted for clarity):

...

import javax.validation.Valid

@RestController
@RequestMapping(path = "/registrations")
public class UserRegistrationController {

    private final RegistrationService registrationService;

    public UserRegistrationController(RegistrationService registrationService) {
        this.registrationService = registrationService;
    }

    @PostMapping(consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
    @ResponseStatus(HttpStatus.CREATED)
    public ExistingUserRegistrationDTO register(@RequestBody @Valid NewUserRegistrationDTO newUserRegistration) {
        UserRegistration userRegistration = registrationService.registerUser(
                newUserRegistration.getEmailAddress(),
                newUserRegistration.getName(), newUserRegistration.getLastName()
        );

        return asDTO(userRegistration);
    }

    private ExistingUserRegistrationDTO asDTO(UserRegistration registration) {
        return new ExistingUserRegistrationDTO(
                registration.getRegistrationId(),
                registration.getEmailAddress(),
                registration.getName(),
                registration.getLastName()
        );
    }

    ...
}

We tell Spring Web to validate incoming request body (@Valid annotation on function argument). If you are using Spring Boot 1.4.x then this will not work without an additional post-processor in Spring Configuration:

@SpringBootApplication
public class WebMvcTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebMvcTestApplication.class, args);
    }

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        return new MethodValidationPostProcessor();
    }
}

Spring Boot 1.5.x is shipped with an additional ValidationAutoConfiguration that automatically creates an instance of MethodValidationPostProcessor if necessary dependencies are present on classpath.

Now, having a REST Controller ready, we need a class to deserialize JSON request into. A simple POJO with Jackson and Javax Validation API annotations is enough to do the trick:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import static com.rg.webmvctest.SystemConstants.EMAIL_REGEXP;

public class NewUserRegistrationDTO {

    private final String emailAddress;
    private final String name;
    private final String lastName;

    @JsonCreator
    public NewUserRegistrationDTO(
            @JsonProperty("email_address")
            String emailAddress,

            @JsonProperty("name")
            String name,

            @JsonProperty("last_name")
            String lastName
    ) {
        this.emailAddress = emailAddress;
        this.name = name;
        this.lastName = lastName;
    }

    @Pattern(regexp = EMAIL_REGEXP, message = "Invalid email address.")
    @NotNull(message = "Email must be provided.")
    public String getEmailAddress() {
        return emailAddress;
    }

    @NotNull(message = "Name must be provided.")
    @Size(min = 2, max = 50, message = "Name must be at least 2 characters and at most 50 characters long.")
    public String getName() {
        return name;
    }

    @NotNull(message = "Last name must be provided.")
    @Size(min = 2, max = 50, message = "Last name must be at least 2 characters and at most 50 characters long.")
    public String getLastName() {
        return lastName;
    }
}

What we have here is a POJO with 3 fields. Each of these fields has Jackson’s @JsonProperty annotation and two more from Javax Validation API.

First test

Writing @WebMvcTest is trivial once you have a framework that supports it. Following example is a minimal working piece of code to create a @WebMvcTest in Spock (written in Groovy):

...
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@WebMvcTest(controllers = [UserRegistrationController])         // 1
class SimplestUserRegistrationSpec extends Specification {

    @Autowired
    protected MockMvc mvc                                       // 2

    @Autowired
    RegistrationService registrationService

    @Autowired
    ObjectMapper objectMapper

    def "should pass user registration details to domain component and return 'created' status"() {
        given:
        Map request = [
                email_address : 'john.wayne@gmail.com',
                name          : 'John',
                last_name     : 'Wayne'
        ]

        and:
        registrationService.registerUser('john.wayne@gmail.com', 'John', 'Wayne') >> new UserRegistration(          // 3
                'registration-id-1',
                'john.wayne@gmail.com',
                'John',
                'Wayne'
        )

        when:
        def results = mvc.perform(post('/registrations').contentType(APPLICATION_JSON).content(toJson(request)))    // 4

        then:
        results.andExpect(status().isCreated())                 // 5

        and:
        results.andExpect(jsonPath('$.registration_id').value('registration-id-1'))        // 5
        results.andExpect(jsonPath('$.email_address').value('john.wayne@gmail.com'))
        results.andExpect(jsonPath('$.name').value('John'))
        results.andExpect(jsonPath('$.last_name').value('Wayne'))
    }

    @TestConfiguration                                          // 6
    static class StubConfig {
        DetachedMockFactory detachedMockFactory = new DetachedMockFactory()

        @Bean
        RegistrationService registrationService() {
            return detachedMockFactory.Stub(RegistrationService)
        }
    }
}

First, there is a @WebMvcTest (1) annotation on the class level. We use it to inform Spring which controllers should be started. In this example, UserRegistrationController is created and mapped onto defined request paths, but to make that happen we have to provide stubs for all dependencies of UserRegistrationController. We do it by writing a custom configuration class and annotating it with @TestConfiguration (6).

Now, when Spring instantiates UserRegistrationController, it passes the stub created in StubConfig as a constructor argument and we are able to perform stubbing in our tests (3). We perform an HTTP request (4) using injected instance of MockMvc (2). Finally, we execute assertions on the obtained instance of org.springframework.test.web.servlet.ResultActions (5). Notice that these were not typical Spock assertions, we used ones built into Spring. Worry not, there is a way to make use of one of the strongest features of Spock:

def "should pass user registration details to domain component and return 'created' status"() {
    given:
    Map request = [
            email_address : 'john.wayne@gmail.com',
            name          : 'John',
            last_name     : 'Wayne'
    ]

    and:
    registrationService.registerUser('john.wayne@gmail.com', 'John', 'Wayne') >> new UserRegistration(
            'registration-id-1',
            'john.wayne@gmail.com',
            'John',
            'Wayne'
    )

    when:
    def response = mvc.perform(
            post('/registrations').contentType(APPLICATION_JSON).content(toJson(request))
    ).andReturn().response  // notice the extra call to: andReturn()

    then:
    response.status == HttpStatus.CREATED.value()

    and:
    with (objectMapper.readValue(response.contentAsString, Map)) {
        it.registration_id == 'registration-id-1'
        it.email_address == 'john.wayne@gmail.com'
        it.name == 'John'
        it.last_name == 'Wayne'
    }
}

What is different with respect to the previous test is the extra call of andReturn() method on the ResultAction object to obtain an HTTP response. Having a response object, we can perform any assertions we need as we would do in any Spock test.

Testing validations

So, let us get back to validations we want to perform on incoming requests. The NewUserRegistrationDTO class has lots of additional annotations that describe what values are allowed for each of the fields. When any of these fields are recognized as having illegal values, Spring throws org.springframework.web.bind.MethodArgumentNotValidException. How do we return a proper HTTP Status and error description in such situation?

First, we tell Spring that we are handling the mapping of MethodArgumentNotValidException onto the ResponseEntity ourselves. We do this by creating a new class and annotating it with org.springframework.web.bind.annotation.ControllerAdvice. Spring recognizes all such classes and they are instantiated as if they were regular Spring Beans. Inside this class, we write a function that handles the mapping. In my sample application, it looks like this:

@ControllerAdvice
public class ExceptionsHandlerAdvice {

    private final ExceptionMapperHelper mapperHelper = new ExceptionMapperHelper();

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorsHolder> handleException(MethodArgumentNotValidException exception) {
        ErrorsHolder errors = new ErrorsHolder(mapperHelper.errorsFromBindResult(exception, exception.getBindingResult()));
        return mapperHelper.mapResponseWithoutLogging(errors, HttpStatus.UNPROCESSABLE_ENTITY);
    }
}

What we have here is a function annotated with org.springframework.web.bind.annotation.ExceptionHandler. Spring recognizes this method and registers it as global exception handler. If MethodArgumentNotValidException is thrown outside of the scope of the Rest Controller, this function is called to produce the response — an instance of org.springframework.http.ResponseEntity. In this case, I have decided to return HTTP Status 422 — UNPROCESSABLE_ENTITY with my own, custom errors structure.

Here is a more complicated example, that shows full test setup (make sure to check the sources on GitHub):

@Unroll
def "should not allow to create a registration with an invalid email address: #emailAddress"() {
    given:
    Map request = [
            email_address : emailAddress,
            name          : 'John',
            last_name     : 'Wayne'
    ]

    when:
    def result = doRequest(
            post('/registrations').contentType(APPLICATION_JSON).content(toJson(request))
    ).andReturn()

    then:
    result.response.status == HttpStatus.UNPROCESSABLE_ENTITY.value()

    and:
    with (objectMapper.readValue(result.response.contentAsString, Map)) {
        it.errors[0].code == 'MethodArgumentNotValidException'
        it.errors[0].path == 'emailAddress'
        it.errors[0].userMessage == userMessage
    }

    where:
    emailAddress              || userMessage
    'john.wayne(at)gmail.com' || 'Invalid email address.'
    'abcdefg'                 || 'Invalid email address.'
    ''                        || 'Invalid email address.'
    null                      || 'Email must be provided.'
}

Summary

This short article by no means covers all features of Spring’s Web Mvc Tests. There are lots of cool features available (testing against Spring Security) and more are coming. JUnit always gets the support first but if you are a Spock fan like me, then I hope you have found this article helpful.