Writing Unit Tests for "Normal" Controllers With Spring MockMvc: Test Slice Configuration

This blog post describes how we can configure the system under test by using Spring Boot's test slice configuration (the @WebMvcTest annotation) when we are writing unit tests for Spring MVC controllers which render data and process form submissions.

After we have finished this blog post, we:

  • Know how we can get the required dependencies.
  • Understand how we can send HTTP requests to the system under test without adding duplicate code to our test suite.
  • Can configure the system under test when we are writing unit tests for view-rendering Spring MVC controllers with JUnit Jupiter.

Let's begin.

This blog post assumes that:

Introduction to System Under Test

The TodoItemCrudController class contains the controller methods which render the todo items queried from the database, create new todo items, and update existing todo items. It has two dependencies:

  • The org.springframework.context.MessageSource provides the localized feedback messages which are displayed on the rendered view.
  • The TodoItemCrudService class provides CRUD operations for todo items. The TodoItemCrudController class invokes its methods when it processes HTTP requests.

Both of these dependencies are injected into the controller class by using constructor injection. The relevant part of the TodoItemCrudController class looks as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/todo-item")
public class TodoItemCrudController {

    private final MessageSource messageSource;
    private final TodoItemCrudService service;

    @Autowired
    public TodoItemCrudController(MessageSource messageSource, 
                                  TodoItemCrudService service) {
        this.messageSource = messageSource;
        this.service = service;
    }
}

Next, we will learn to get the required dependencies.

Getting the Required Dependencies

The first thing that we have to do is to ensure that our build script declares the required testing dependencies. Our test suite uses the following testing tools:

  • JUnit Jupiter. A new programming and extension model that is used to write both tests and extensions with JUnit.
  • Spring MockMvc. A framework provided by Spring Test. Spring MockMvc allows us to send simulated HTTP requests to our controller without running a real servlet container.
  • Spring Boot WebMvc Test. A Spring Boot testing module that provides the @WebMvcTest annotation. It auto-configures the Spring Web MVC infrastructure by loading an application context that contains only the components required to test the web layer (such as controllers, filters, and custom web layer configuration) and auto-configures a MockMvc bean that can be injected into a test class.
  • Hamcrest. An assertion library that's used to write assertions for the HTTP response returned by the system under test.
  • Mockito. A mocking framework that's used to replace the dependencies of the system under test with test doubles.
  • AssertJ. An assertion library that allows us to write readable assertions with its fluent API.

Because we must use Spring Boot if we want to use the @WebMvcTest annotation, we must use Spring Boot dependency management when we declare dependencies in our build script. The benefit of this approach is that we don't have to manage dependency versions and worry about the compatibility of individual dependencies. Spring Boot dependency management takes care of these things for us. We can get the required testing dependencies by declaring the spring-boot-starter-test and spring-boot-webmvc-test dependencies in our build script.

Let's move on and find out how we can create a request builder class.

Creating the Request Builder Class

When we write unit tests for a real-life web application or a REST API, we notice that every test method creates a new HTTP request and sends it to the system under test. If we create and send these HTTP requests by using the MockMvc API in our test methods, we add a lot of duplicate code to our test suite. This is a problem because duplicate code makes our tests hard to write and maintain.

We can solve this problem by using request builder classes. A request builder class is a class that fulfills these conditions:

  • It contains methods which create and send HTTP requests to the system under test by using a MockMvc object.
  • Every method must return a ResultActions object which allows us to write assertions for the returned HTTP response.

We can write our request builder class by following these steps:

  1. Create a new class.
  2. Add a private MockMvc field to the created class. Our request builder class will use this field when it creates and sends HTTP requests to the system under test.
  3. Ensure that we can inject the used MockMvc object into the mockMvc field by using constructor injection.

After we have created our request builder class, its source code looks as follows:

import org.springframework.test.web.servlet.MockMvc;

class TodoItemRequestBuilder {

    private final MockMvc mockMvc;

    TodoItemRequestBuilder(MockMvc mockMvc) {
        this.mockMvc = mockMvc;
    }
}
There are two things I want to point out:

  • Our request builder class is package private because we should put it to the same package as the test class that uses it.
  • This request builder class is useless because we didn’t write the methods which create and send HTTP requests to the system under test. We will talk more about request builder classes when we learn to write unit tests for view-rendering Spring MVC controllers.

Additional Reading:

Let's move on and find out how we can configure the system under test.

Configuring the System Under Test

We can configure the system under test by following these steps:

First, create a new test configuration class that configures a StaticMessageSource bean that can be injected into a test class. Our setup methods will use this bean when they configure the feedback messages that should be shown on the rendered view.

The source code of our test configuration class looks as follows:

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.StaticMessageSource;

@TestConfiguration
class TestMessageSourceConfiguration {

    @Bean
    public StaticMessageSource messageSource() {
        return new StaticMessageSource();
    }
}

Second, create a new test class and configure the system under test by following these steps:

  1. Annotate the test class with the @WebMvcTest annotation and specify that we want to write tests for the TodoItemCrudController class.
  2. Import the bean definitions from our test configuration class into the loaded application context.
  3. Add a private messageSource field to our test class and inject the StaticMessageSource bean into this field.
  4. Add a private mockMvc field to our test class and inject the MockMvc bean into this field.
  5. Add a private service field to our test class and ensure that a TodoItemCrudService mock is injected into this field.
  6. Add a private requestBuilder field to our test class. This field contains a TodoItemRequestBuilder object that's used to send HTTP requests to the system under test.
  7. Create a setup method that's run before a test method is run.
  8. Implement the setup method by creating a new TodoItemRequestBuilder object and storing the created object in the requestBuilder field.

After we have configured the system under test, the source code of our test class looks as follows:

import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(TodoItemCrudController.class)
@Import(TestMessageSourceConfiguration.class)
class TodoItemCrudControllerTest {

    @Autowired
    private StaticMessageSource messageSource;

    @Autowired
    private MockMvc mockMvc;

    @MockitoBean
    private TodoItemCrudService service;

    private TodoItemRequestBuilder requestBuilder;

    @BeforeEach
    void createRequestBuilder() {
        requestBuilder = new TodoItemRequestBuilder(mockMvc);
    }
}

We can now configure the system under test by using the test slice configuration. Let’s summarize what we learned from this blog post.

Summary

This blog post has taught us that:

  • If we want to use the @WebMvcTest annotation, we must use Spring Boot.
  • We must use Spring Boot dependency management and get the required dependencies by declaring the spring-boot-starter-test and spring-boot-webmvc-test dependencies in our build script.
  • We can send HTTP requests to the system under test without writing duplicate code by using a request builder class.
  • If we want to load an application context that contains only web components, we must annotate our test class with the @WebMvcTest annotation.
  • We can import beans into the loaded application context by using the @Import annotation.
  • If we have to replace a dependency of the tested controller with a Mockito mock, we must add a new field to our test class and annotate that field with the @MockitoBean annotation.

P.S. You can get the example application of this blog post from GitHub.

0 comments… add one

Leave a Reply