Spring WebMVC Test Overview Spring MVC Controllers are tricky to test properly. They have a high degree of integration with Spring MVC framework. JUnit tests are not sufficient to test the framework interaction. Spring Boot supports a concept of Test Splices which bring up a targeted segment of the autoconfigured Spring Boot environment: e.g. just the Database components or just the Web components, user defined Spring beans typically are not initialized. WebMvcTest @WebMvcTest A Spring Boot test splice which creates a MockMVC environment for the Controller under a test. Dependencies of it are not included and need to be added to the Spring Context in the test environment. @WebMvcTest will autoconfigure all the Controllers. If you want to autoconfigure only specific Controllers, use it like: @WebMvcTest(BookController.class) 🔴 MockMvc Main entry point for server-side Spring MVC test support. In @WebMvcTest you can autowire 🔴 MockMvc @Autowired MockMvc mockMvc; Testing Controller requests 🟠MockMvcRequestBuilders Static factory methods for building 🟢 MockHttpServletRequest which is mock implementation of the ⚪ HttpServletRequest. 🟠MockMvcResultMatchers Static factory methods for building ⚪ ResultMatcher-based result actions. A ⚪ ResultMatcher matches the result of an executed request against some expectation. Test Controller GET request mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/book/{bookId}", testBookDTO.getId()) .accept(MediaType.APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)); Test Controller POST request mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/book") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(testBookDTO))) (1) .andExpect(MockMvcResultMatchers.status().isCreated()) .andExpect(MockMvcResultMatchers.header().exists(HttpHeaders.LOCATION)); 1 Converts testBookDTO into JSON with FasterXML Jackson Test Controller PUT request mockMvc.perform(MockMvcRequestBuilders.put("/api/v1/book/{bookId}", testBookDTO.getId()) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(testBookDTO))) .andExpect(MockMvcResultMatchers.status().isNoContent()); Test Controller PATCH request Map<String, Object> bookMap = new HashMap<>(); bookMap.put("name", "New Book Name"); mockMvc.perform(MockMvcRequestBuilders.patch("/api/v1/book/{bookId}", testBookDTO.getId()) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookMap))) .andExpect(MockMvcResultMatchers.status().isNoContent()); Test Controller DELETE request mockMvc.perform(MockMvcRequestBuilders.delete("/api/v1/book/{bookId}", testBookDTO.getId()) .accept(MediaType.APPLICATION_JSON) .andExpect(MockMvcResultMatchers.status().isNoContent()); Mocking Beans @MockitoBean Can be used in test classes to override a bean in the test’s ⚪ ApplicationContext with a Mockito mock. For example a Service which is called by Controller. Return an object on given method call @MockitoBean BookService bookService; @Test void testGetBookById() throws Exception { BookDTO testBookDTO = // ... BDDMockito.given(bookService.getBookById(ArgumentMatchers.any(UUID.class))) (1) (2) .willReturn(testBookDTO); // ... } 1 🟢 BDDMockito - Behavior Driven Development style of writing tests uses //given //when //then comments as fundamental parts of your test methods. 2 🟢 ArgumentMatchers - allows flexible verification or stubbing. For example any() matches anything, including nulls, while any(Class<T> type) matches any object of given type, excluding nulls. Verify that behavior happened once Mockito.verify(bookService).updateBookById(ArgumentMatchers.any(UUID.class), ArgumentMatchers.any(BookDTO.class)); (1) 1 🟢 Mockito#verify(T mock) - verifies certain behavior happened once. It is alias to: verify(mock, Mockito.times(1)).someMethod("some arg");. Capture and assert arguments @Captor (1) ArgumentCaptor<UUID> uuidArgumentCaptor; @Test void testDeleteBookById() throws Exception { BookDTO testBookDTO = // ... // ... Mockito.verify(bookService).deleteBookById(uuidArgumentCaptor.capture()); Assertions.assertThat(testBookDTO.getId()).isEqualTo(uuidArgumentCaptor.getValue()); (2) } 1 @Captor - allows shorthand 🟢 ArgumentCaptor creation on fields (instead of ArgumentCaptor<UUID> uuidArgumentCaptor = ArgumentCaptor.forClass(UUID.class)). 2 🟢 Assertions - AssertJ entry point for assertion methods for different types. Each method in this class is a static factory for a type-specific assertion object. Throw an exception on given method call BDDMockito.given(bookService.getBookById(ArgumentMatchers.any(UUID.class))) .willThrow(NotFoundException.class); Jayway JsonPath A Java DSL (Domain Specific Language) for reading JSON documents. It is included in Spring Boot Test dependency. Useful for performing assertions against the JSON object that is coming back from 🔴 MockMvc. JsonPath expressions can use the dot-notation: $.store.book[0].title or the bracket-notation: $['store']['book'][0]['title'] Example of 🔴 MockMvc json path validation .andExpect(MockMvcResultMatchers.jsonPath("$.content[1].id", Is.is("some-id-123"))) (1) .andExpect(MockMvcResultMatchers.jsonPath("$.content.length()", Is.is(3))); (2) 1 🟢 Is#is(T value) - matcher from JavaHamcrest which is a shortcut to the frequently used is(equalTo(x)) (where is(Matcher<T> matcher) only decorates another matcher). 2 Asserts that we have 3 objects in content array. Testing Exception Handling Test 404 HTTP Response Code mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/book/{bookId}", UUID.randomUUID())) .andExpect(status().isNotFound());