1. Overview
Let’s dive into the world of Spring Boot testing! In this tutorial, we’ll take a deep dive into the @SpringBootTest and @WebMvcTest annotations. We’ll explore when and why to use each one and how they work together to test our Spring Boot applications. Plus, we’ll uncover the inner workings of MockMvc and how it interacts with both annotations in integration tests.
2. What Are @WebMvcTest and @SpringBootTest
The @WebMvcTest annotation is used to create MVC (or more specifically controller) related tests. It can also be configured to test for a specific controller. It mainly loads and makes testing of the web layer easy.
The @SpringBootTest annotation is used to create a test environment by loading a full application context (like classes annotated with @Component and @Service, DB connections, etc). It looks for the main class (which has the @SpringBootApplication annotation) and uses it to start the application context.
Both of these annotations were introduced in Spring Boot 1.4.
3. Project Setup
For this tutorial, we’ll create two classes namely SortingController and SortingService. SortingController receives a request with a list of integers and uses a helper class SortingService which has the business logic to sort the list.
We’ll be using constructor injection to get SortingService dependency as shown below:
@RestController
public class SortingController {
private final SortingService sortingService;
public SortingController(SortingService sortingService){
this.sortingService=sortingService;
} // ...
}
Let’s declare a GET method to check our server running and this will also help us explore the working of annotations during testing:
@GetMapping
public ResponseEntity<String> helloWorld(){
return ResponseEntity.ok("Hello, World!");
}
Next, we’ll also have a post method that takes an array as a JSON body and returns a sorted array in response. Testing this type of method will help us to understand the use of MockMvc:
@PostMapping
public ResponseEntity<List<Integer>> sort(@RequestBody List<Integer> arr){
return ResponseEntity.ok(sortingService.sortArray(arr));
}
4. Comparing @SpringBootTest and @WebMvcTest
The @WebMvcTest annotation is located in the org.springframework.boot.test.autoconfigure.web.servlet package, whereas @SpringBootTest is located in org.springframework.boot.test.context. Spring Boot, by default, adds the necessary dependencies to our project assuming that we plan to test our application. At the class level, we can use either one of them at a time.
4.1. Using MockMvc
In a @SpringBootTest context, MockMvc will automatically call the actual service implementation from the controller. The service layer beans will be available in the application context. To use MockMvc within our tests, we’ll need to add the @AutoConfigureMockMvc annotation. This annotation creates an instance of MockMvc, injects it into the mockMvc variable, and makes it ready for testing without requiring manual configuration:
@AutoConfigureMockMvc
@SpringBootTest
class SortingControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
}
In @WebMvcTest, MockMvc will be accompanied by @MockBean of the service layer to mock service layer responses without calling the real service. Also, service layer beans are not included in the application context. It provides @AutoConfigureMockMvc by default:
@WebMvcTest
class SortingControllerUnitTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private SortingService sortingService;
}
Note: When using @SpringBootTest with webEnvironment=RANDOM_PORT, be cautious about using MockMvc because MockMvc tries to make sure that whatever is needed for handling web requests is in place and no servlet container (handles incoming HTTP requests and generates responses) is started while webEnvironment=RANDOM_PORT tries to bring up the servlet container. They both contradict each other if used in conjunction.
4.2. What Is Autoconfigured?
In @WebMvcTest, Spring Boot automatically configures the MockMvc instance, DispatcherServlet, HandlerMapping, HandlerAdapter, and ViewResolvers. It also scans for @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, WebMvcConfigurer, and HandlerMethodArgumentResolver components. In general, it autoconfigures web layer-related components.
@SpringBootTest loads everything that @SpringBootApplication (SpringBootConfiguration+ EnableAutoConfiguration + ComponentScan) does i.e. a fully-fledged application context. It even loads the application.properties file and the profile-related info. It also enables beans to inject like using @Autowired.
4.3. Lightweight or Heavyweight
We can say that @SpringBootTest is heavyweight as it is mostly configured for integration testing by default unless we want to use any mocks. It also has all the beans in the application context. This also is the reason for it being slower as compared to others.
On the other hand, @WebMvcTest is more isolated and only concerned about the MVC layer. It is ideal for unit testing. We can be specific to one or more controllers also. It has a limited number of beans in the application context. Also, while running we can observe the same time difference (i.e. less running time with @WebMvcTest) for test cases to complete.
4.4. Web Environment During Testing
When we start a real application, we usually hit “http://localhost:8080” to access our application. To imitate the same scenario during testing we use webEnvironment. And using this we define a port for our test cases (similar to 8080 in URL). @SpringBootTest can step in both the simulated webEnvironment (WebEnvironment.MOCK) or real webEnvironment (WebEnvironment.RANDOM_PORT) while @WebMvcTest provides only the simulated test environment.
Following is the code example for it @SpringBootTest with WebEnvironment:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SortingControllerWithWebEnvironmentIntegrationTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private ObjectMapper objectMapper;
}
Now let’s use them in action for writing test cases. Following is the test case for the GET method:
@Test
void whenHelloWorldMethodIsCalled_thenReturnSuccessString() {
ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:" + port + "/", String.class);
Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());
Assertions.assertEquals("Hello, World!", response.getBody());
}
Following is the test case to check the correctness of the POST method:
@Test
void whenSortMethodIsCalled_thenReturnSortedArray() throws Exception {
List<Integer> input = Arrays.asList(5, 3, 8, 1, 9, 2);
List<Integer> sorted = Arrays.asList(1, 2, 3, 5, 8, 9);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
ResponseEntity<List> response = restTemplate.postForEntity("http://localhost:" + port + "/",
new HttpEntity<>(objectMapper.writeValueAsString(input), headers),
List.class);
Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());
Assertions.assertEquals(sorted, response.getBody());
}
4.5. Dependencies
@WebMvcTest does not detect dependencies needed for the controller automatically, so we’ve to Mock them. While @SpringBootTest does it automatically.
Here we can see we’ve used @MockBean because we’re calling a service from inside a controller:
@WebMvcTest
class SortingControllerUnitTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private SortingService sortingService;
}
Now let’s see a test example of using MockMvc with a mocked bean:
@Test
void whenSortMethodIsCalled_thenReturnSortedArray() throws Exception {
List<Integer> input = Arrays.asList(5, 3, 8, 1, 9, 2);
List<Integer> sorted = Arrays.asList(1, 2, 3, 5, 8, 9);
when(sortingService.sortArray(input)).thenReturn(sorted);
mockMvc.perform(post("/").contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(input)))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(sorted)));
}
Here we’ve used when().thenReturn() to mock the sortArray() function in our service class. Not doing that will cause a NullPointerException.
4.6. Customization
@SpringBootTest is mostly not a good choice for customization but @WebMvcTest can be customized to work with only limited controller classes. In the following example, I have mentioned the SortingController class specifically. So only one controller with its dependencies is registered with the application:
@WebMvcTest(SortingController.class)
class SortingControllerUnitTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private SortingService sortingService;
@Autowired
private ObjectMapper objectMapper;
}
5. Conclusion
@SpringBootTest and @WebMvcTest, each serve distinct purposes. @WebMvcTest is designed for MVC-related tests, focusing on the web layer and providing easy testing for specific controllers. On the other hand, @SpringBootTest creates a test environment by loading a full application context, including @Components, DB connections, and @Service, making it suitable for integration and system testing, similar to the production environment.
When it comes to using MockMvc, @SpringBootTest internally calls actual service implementation from the controller, while @WebMvcTest is accompanied by @MockBean for mocking service layer responses without calling the real service.
As usual, the code for this tutorial is available over on GitHub.