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.
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.MessageSourceprovides the localized feedback messages which are displayed on the rendered view. - The
TodoItemCrudServiceclass provides CRUD operations for todo items. TheTodoItemCrudControllerclass 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
@WebMvcTestannotation. 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 aMockMvcbean 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.
- Spring Boot User Guide: Using the Maven Plugin
- Spring Boot User Guide: Managing Dependencies With the Gradle Plugin
Additional Reading:
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
MockMvcobject. - Every method must return a
ResultActionsobject which allows us to write assertions for the returned HTTP response.
We can write our request builder class by following these steps:
- Create a new class.
- Add a
private MockMvcfield to the created class. Our request builder class will use this field when it creates and sends HTTP requests to the system under test. - Ensure that we can inject the used
MockMvcobject into themockMvcfield 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;
}
}
- 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:
- Annotate the test class with the
@WebMvcTestannotation and specify that we want to write tests for theTodoItemCrudControllerclass. - Import the bean definitions from our test configuration class into the loaded application context.
- Add a
privatemessageSourcefield to our test class and inject theStaticMessageSourcebean into this field. - Add a
privatemockMvcfield to our test class and inject theMockMvcbean into this field. - Add a
privateservicefield to our test class and ensure that aTodoItemCrudServicemock is injected into this field. - Add a
privaterequestBuilderfield to our test class. This field contains aTodoItemRequestBuilderobject that's used to send HTTP requests to the system under test. - Create a setup method that's run before a test method is run.
- Implement the setup method by creating a new
TodoItemRequestBuilderobject and storing the created object in therequestBuilderfield.
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
@WebMvcTestannotation, we must use Spring Boot. - We must use Spring Boot dependency management and get the required dependencies by declaring the
spring-boot-starter-testandspring-boot-webmvc-testdependencies 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
@WebMvcTestannotation. - We can import beans into the loaded application context by using the
@Importannotation. - 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
@MockitoBeanannotation.
P.S. You can get the example application of this blog post from GitHub.