Java Spring Junit5 Controller Testing Examples
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
– @RestControllerAdvice example in Spring Boot
– Spring Boot Token based Authentication with Spring Security example
Associations:
– 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
- Spring Boot @WebMvcTest
- Spring Boot test Rest Controller example
- Technology
- Project Structure
- Setup Spring Boot Test Rest Controller Project
- Spring Boot @WebMvcTest example
- Run Spring Boot Rest API unit testing with Junit 5
- Conclusion
- Further Reading
- Source Code
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/tutorials -
shouldReturnTutorial()
: GET /api/tutorials/[:id] -
shouldReturnNotFoundTutorial()
: GET /api/tutorials/[:id] – 404 -
shouldReturnListOfTutorials()
: GET /api/tutorials -
shouldReturnListOfTutorialsWithFilter()
: GET /api/tutorials?title=[:title] -
shouldReturnNoContentWhenFilter()
: GET /api/tutorials?title=[:title] – 204 -
shouldUpdateTutorial()
: PUT /api/tutorials/[:id] -
shouldReturnNotFoundUpdateTutorial()
: PUT /api/tutorials/[:id] – 404 -
shouldDeleteTutorial()
: 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
- @WebMvcTest annotation
- Auto-configured Spring MVC Tests
More Practice:
– @DataJpaTest example in Spring Boot
– @RestControllerAdvice example in Spring Boot
– Spring Boot Token based Authentication with Spring Security example
Associations:
– 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
Source: https://www.bezkoder.com/spring-boot-webmvctest/
0 Response to "Java Spring Junit5 Controller Testing Examples"
Postar um comentário