Nowadays Unit Test is so important in Software Development, and Spring Boot Test also provides @WebMvcTest
annotation to make writing unit test for Rest Controller more simpler. In this tutorial, we’re gonna look at how to apply @WebMvcTest
in our Spring Boot Project with JUnit 5 and Mockito.
More Practice:
– @DataJpaTest example in Spring Boot
– Validate Request Body in Spring Boot
– @RestControllerAdvice example in Spring Boot
– Spring Boot Token based Authentication with Spring Security example
– Documentation: Spring Boot + Swagger 3 example (with OpenAPI 3)
– Caching: Spring Boot Redis Cache example
Associations:
– Spring Boot One To One example with JPA, Hibernate
– Spring Boot One To Many example with JPA, Hibernate
– Spring Boot Many to Many example with JPA, Hibernate
Contents
Spring Boot test Rest Controller Overview
We’ve created Rest Controller for CRUD Operations and finder method.
Let look at the code:
(step by step to build the Rest APIs is in:
– Spring Boot + H2
– Spring Boot + MySQL
– Spring Boot + PostgreSQL
– Spring Data + MongoDB
– Spring JPA + SQL Server
– Spring JPA + Oracle
– Spring Data + Cassandra)
@RestController
public class TutorialController {
@Autowired
TutorialRepository tutorialRepository;
@GetMapping("/tutorials")
public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) {
try {
...
return new ResponseEntity<>(tutorials, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/tutorials/{id}")
public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") long id) {
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
if (tutorialData.isPresent()) {
return new ResponseEntity<>(tutorialData.get(), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@PutMapping("/tutorials/{id}")
public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
if (tutorialData.isPresent()) {
...
return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
...
@DeleteMapping("/tutorials/{id}")
public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") long id) {
try {
tutorialRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@DeleteMapping("/tutorials")
public ResponseEntity<HttpStatus> deleteAllTutorials() {
// try and catch
}
}
Is there any way to test Rest Controller in Spring Boot? Any way to:
– know that an HTTP request is mapped to correct endpoints (with @GetMapping
, @PostMapping
, @PutMapping
and @DeleteMapping
)
– know that path variables are mapped with @PathVariable
– know that the @RequestBody
and @ResponseBody
work correctly
Let’s write the test cases with @WebMvcTest
and Mockito, then run Spring Boot test Rest Controller with JUnit 5.
For testing, we’ll work with H2 in-memory database. It eliminates the need for configuring and starting an actual database.
Spring Boot @WebMvcTest
Spring Boot @WebMvcTest annotation provides simple way to test Rest Controller, it disables full auto-configuration (@Component, @Service or @Repository beans will not be scanned) and apply only configuration relevant to the web layer (@Controller, @ControllerAdvice, @JsonComponent, WebMvcConfigurer beans…).
If you have multiple controllers, you can make only one instantiate by using @WebMvcTest(TutorialController.class)
.
@WebMvcTest(TutorialController.class)
public class TutorialControllerTests {
}
Now look at our Rest Controller (TutorialController
) which has a dependency component TutorialRepository
:
@RestController
public class TutorialController {
@Autowired
TutorialRepository tutorialRepository;
...
}
Spring automatically injects the dependency into the controller.
Let’s think about the test class.
– We need to use @MockBean
annotation to create and inject a mock of the TutorialRepository
. It helps application context start), then we will set the expectations using Mockito
.
– We need to fake HTTP requests. So we will autowire a MockMvc
bean which Spring Boot autoconfigures.
@WebMvcTest(TutorialController.class)
public class TutorialControllerTests {
@MockBean
private TutorialRepository tutorialRepository;
@Autowired
private MockMvc mockMvc;
@Test
void shouldDoSomething() throws Exception {
// set expectation for TutorialRepository using Mockito
when(tutorialRepository...).thenReturn(...);
// perform HTTP request and set the expectations with MockMVC
mockMvc.perform(...).andExpect(...).andExpect(...);
}
}
Spring Boot test Rest Controller example
Technology
- Spring Boot 2
- Mockito 4
- Junit 5
- Maven 3.8.5
- H2 database
Project Structure
– Tutorial
data model class corresponds to entity and table tutorials.
– TutorialRepository
handles CRUD methods and custom finder methods. It will be autowired in TutorialController
and mocked in TutorialControllerTests
.
– TutorialControllerTests
is the main Test Class used for testing Rest Controller and annotated with @WebMvcTest
.
– pom.xml contains dependencies for Spring Boot Test, Web, Spring Data, H2 database.
Setup Spring Boot Test Rest Controller Project
This tutorial gives you an additional unit test for following Rest APIs example:
– Spring Boot + H2
– Spring Boot + MySQL
– Spring Boot + PostgreSQL
– Spring Data + MongoDB
– Spring JPA + SQL Server
– Spring JPA + Oracle
– Spring Data + Cassandra
So you only need to create a new file named TutorialControllerTests.java
in src/test/java/[package]
.
This is a typical pom.xml with dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
Spring Boot @WebMvcTest example
Let’s apply @WebMvcTest
in our Spring Boot Rest API Unit Test with JUnit 5 and Mockito.
In src/test/java/com.bezkoder.spring.test, create a file with name: TutorialControllerTests.java
.
Inside the file, we will define TutorialControllerTests
to unit test the Rest API endpoints which has following methods:
shouldCreateTutorial()
: POST /api/tutorialsshouldReturnTutorial()
: GET /api/tutorials/[:id]shouldReturnNotFoundTutorial()
: GET /api/tutorials/[:id] – 404shouldReturnListOfTutorials()
: GET /api/tutorialsshouldReturnListOfTutorialsWithFilter()
: GET /api/tutorials?title=[:title]shouldReturnNoContentWhenFilter()
: GET /api/tutorials?title=[:title] – 204shouldUpdateTutorial()
: PUT /api/tutorials/[:id]shouldReturnNotFoundUpdateTutorial()
: PUT /api/tutorials/[:id] – 404shouldDeleteTutorial()
: DELETE /api/tutorials/[:id]shouldDeleteAllTutorials()
: DELETE /api/tutorials
package com.bezkoder.spring.test;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import com.bezkoder.spring.test.controller.TutorialController;
import com.bezkoder.spring.test.model.Tutorial;
import com.bezkoder.spring.test.repository.TutorialRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
@WebMvcTest(TutorialController.class)
public class TutorialControllerTests {
@MockBean
private TutorialRepository tutorialRepository;
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
void shouldCreateTutorial() throws Exception {
Tutorial tutorial = new Tutorial(1, "Spring Boot @WebMvcTest", "Description", true);
mockMvc.perform(post("/api/tutorials").contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(tutorial)))
.andExpect(status().isCreated())
.andDo(print());
}
@Test
void shouldReturnTutorial() throws Exception {
long id = 1L;
Tutorial tutorial = new Tutorial(id, "Spring Boot @WebMvcTest", "Description", true);
when(tutorialRepository.findById(id)).thenReturn(Optional.of(tutorial));
mockMvc.perform(get("/api/tutorials/{id}", id)).andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(id))
.andExpect(jsonPath("$.title").value(tutorial.getTitle()))
.andExpect(jsonPath("$.description").value(tutorial.getDescription()))
.andExpect(jsonPath("$.published").value(tutorial.isPublished()))
.andDo(print());
}
@Test
void shouldReturnNotFoundTutorial() throws Exception {
long id = 1L;
when(tutorialRepository.findById(id)).thenReturn(Optional.empty());
mockMvc.perform(get("/api/tutorials/{id}", id))
.andExpect(status().isNotFound())
.andDo(print());
}
@Test
void shouldReturnListOfTutorials() throws Exception {
List<Tutorial> tutorials = new ArrayList<>(
Arrays.asList(new Tutorial(1, "Spring Boot @WebMvcTest 1", "Description 1", true),
new Tutorial(2, "Spring Boot @WebMvcTest 2", "Description 2", true),
new Tutorial(3, "Spring Boot @WebMvcTest 3", "Description 3", true)));
when(tutorialRepository.findAll()).thenReturn(tutorials);
mockMvc.perform(get("/api/tutorials"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.size()").value(tutorials.size()))
.andDo(print());
}
@Test
void shouldReturnListOfTutorialsWithFilter() throws Exception {
List<Tutorial> tutorials = new ArrayList<>(
Arrays.asList(new Tutorial(1, "Spring Boot @WebMvcTest", "Description 1", true),
new Tutorial(3, "Spring Boot Web MVC", "Description 3", true)));
String title = "Boot";
MultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>();
paramsMap.add("title", title);
when(tutorialRepository.findByTitleContaining(title)).thenReturn(tutorials);
mockMvc.perform(get("/api/tutorials").params(paramsMap))
.andExpect(status().isOk())
.andExpect(jsonPath("$.size()").value(tutorials.size()))
.andDo(print());
tutorials = Collections.emptyList();
when(tutorialRepository.findByTitleContaining(title)).thenReturn(tutorials);
mockMvc.perform(get("/api/tutorials").params(paramsMap))
.andExpect(status().isNoContent())
.andDo(print());
}
@Test
void shouldReturnNoContentWhenFilter() throws Exception {
String title = "BezKoder";
MultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>();
paramsMap.add("title", title);
List<Tutorial> tutorials = Collections.emptyList();
when(tutorialRepository.findByTitleContaining(title)).thenReturn(tutorials);
mockMvc.perform(get("/api/tutorials").params(paramsMap))
.andExpect(status().isNoContent())
.andDo(print());
}
@Test
void shouldUpdateTutorial() throws Exception {
long id = 1L;
Tutorial tutorial = new Tutorial(id, "Spring Boot @WebMvcTest", "Description", false);
Tutorial updatedtutorial = new Tutorial(id, "Updated", "Updated", true);
when(tutorialRepository.findById(id)).thenReturn(Optional.of(tutorial));
when(tutorialRepository.save(any(Tutorial.class))).thenReturn(updatedtutorial);
mockMvc.perform(put("/api/tutorials/{id}", id).contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updatedtutorial)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.title").value(updatedtutorial.getTitle()))
.andExpect(jsonPath("$.description").value(updatedtutorial.getDescription()))
.andExpect(jsonPath("$.published").value(updatedtutorial.isPublished()))
.andDo(print());
}
@Test
void shouldReturnNotFoundUpdateTutorial() throws Exception {
long id = 1L;
Tutorial updatedtutorial = new Tutorial(id, "Updated", "Updated", true);
when(tutorialRepository.findById(id)).thenReturn(Optional.empty());
when(tutorialRepository.save(any(Tutorial.class))).thenReturn(updatedtutorial);
mockMvc.perform(put("/api/tutorials/{id}", id).contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updatedtutorial)))
.andExpect(status().isNotFound())
.andDo(print());
}
@Test
void shouldDeleteTutorial() throws Exception {
long id = 1L;
doNothing().when(tutorialRepository).deleteById(id);
mockMvc.perform(delete("/api/tutorials/{id}", id))
.andExpect(status().isNoContent())
.andDo(print());
}
@Test
void shouldDeleteAllTutorials() throws Exception {
doNothing().when(tutorialRepository).deleteAll();
mockMvc.perform(delete("/api/tutorials"))
.andExpect(status().isNoContent())
.andDo(print());
}
}
Run Spring Boot Rest API unit testing with Junit 5
– First run command: mvn clean install
.
– Then run Test: mvn test -Dtest=TutorialControllerTests
Here are several examples and the result:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/tutorials/1
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = com.bezkoder.spring.test.controller.TutorialController
Method = com.bezkoder.spring.test.controller.TutorialController#getTutorialById(long)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Content-Type:"application/json"]
Content type = application/json
Body = {"id":1,"title":"Spring Boot @WebMvcTest","description":"Description","published":true}
Forwarded URL = null
Redirected URL = null
Cookies = []
[... ... ...]
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/tutorials
Parameters = {title=[Boot]}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = com.bezkoder.spring.test.controller.TutorialController
Method = com.bezkoder.spring.test.controller.TutorialController#getAllTutorials(String)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Content-Type:"application/json"]
Content type = application/json
Body = [{"id":1,"title":"Spring Boot @WebMvcTest","description":"Description 1","published":true},{"id":3,"title":"Spring Boot Web MVC","description":"Description 3","published":true}]
Forwarded URL = null
Redirected URL = null
Cookies = []
[... ... ...]
MockHttpServletRequest:
HTTP Method = PUT
Request URI = /api/tutorials/1
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"67"]
Body = {"id":1,"title":"Updated","description":"Updated","published":true}
Session Attrs = {}
Handler:
Type = com.bezkoder.spring.test.controller.TutorialController
Method = com.bezkoder.spring.test.controller.TutorialController#updateTutorial(long, Tutorial)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Content-Type:"application/json"]
Content type = application/json
Body = {"id":1,"title":"Updated","description":"Updated","published":true}
Forwarded URL = null
Redirected URL = null
Cookies = []
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/tutorials
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"87"]
Body = {"id":1,"title":"Spring Boot @WebMvcTest","description":"Description","published":true}
Session Attrs = {}
Handler:
Type = com.bezkoder.spring.test.controller.TutorialController
Method = com.bezkoder.spring.test.controller.TutorialController#createTutorial(Tutorial)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 201
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
[... ... ...]
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.436 s - in com.bezkoder.spring.test.TutorialControllerTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.151 s
[INFO] Finished at: 2022-03-28T10:15:22+07:00
[INFO] ------------------------------------------------------------------------
We have another way to run Junit 5 test in Spring Tool Suite with UI.
In Package Explorer, Right Click on TutorialControllerTests.java -> Run as -> JUnit Test:
Conclusion
Today we’ve create Spring Boot Test for Rest Controller with Junit 5 using @WebMvcTest
and Mockito. We also run unit test for many CRUD and finder methods endpoints.
You may need to handle Exception with:
Spring Boot @ControllerAdvice & @ExceptionHandler example
Happy learning! See you again.
Further Reading
More Practice:
– @DataJpaTest example in Spring Boot
– @RestControllerAdvice example in Spring Boot
– Spring Boot Token based Authentication with Spring Security example
– Validate Request Body in Spring Boot
Associations:
– Spring Boot One To One example with JPA, Hibernate
– Spring Boot One To Many example with JPA, Hibernate
– Spring Boot Many to Many example with JPA, Hibernate
Source Code
You can find the complete source code for this tutorial on Github.
The code gives you an additional unit test for following Rest APIs example:
– Spring Boot + H2
– Spring Boot + MySQL
– Spring Boot + PostgreSQL
– Spring Data + MongoDB
– Spring JPA + SQL Server
– Spring JPA + Oracle
– Spring Data + Cassandra
Documentation: Spring Boot + Swagger 3 example (with OpenAPI 3)
Caching: Spring Boot Redis Cache example