HelloKoding

Practical coding guides

Spring Boot MockMvc and @WebMvcTest Example of Integration Test REST API and Controller Layer

In this tutorial, you will learn to implement a unit test of the REST API and Controller layer in Spring Boot by using @WebMvcTest and MockMvc

@WebMvcTest is used in combination with @RunWith(SpringRunner.class) when a test focuses only on Spring MVC components. It provides the following features

  • Disable full auto-configuration (not @Component, @Service or @Repository beans) and instead apply only configuration relevant to MVC tests such as @Controller and @ControllerAdvice
  • Auto-configure Spring Security and MockMvc

Project dependencies

Include spring-boot-starter-test into your pom.xml file

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

spring-boot-starter-test contains some testing support libraries such as JUnit, Spring Test + Spring Boot Test, Mockito, AssertJ, Hamcrest and JsonPath

Define the test class

Run the tests with SpringRunner and @WebMvcTest

  • Use the @RunWith(SpringRunner.class) class annotation to tell JUnit to run the unit tests in Spring’s testing supports
  • Use the @WebMvcTest class annotation to test only Spring MVC components

Inject MockMVC with @Autowired

  • Inject the MockMVC field into your test class, for example
@Autowired
private MockMvc mockMvc;

Mock dependencies with @MockBean

  • Use @MockBean to mock API and Controller dependencies to the Spring ApplicationContext, for example
@MockBean
private ProductService productService;

@MockBean
private ProductMapper productMapper;

Stub methods with Mockito’s doReturn…when

  • Use Mockito.doReturn(...).when(aMock).doSomething(...) to give the test input, for example
// given
ProductDTO productDTO = ProductDTO.builder()
    .name("P1")
    .description("P1 desc")
    .price(new BigDecimal("1"))
    .build();

List<ProductDTO> productDTOs = Arrays.asList(productDTO);

doReturn(new ArrayList<>()).when(productService).findAll();     

doReturn(productDTOs).when(productMapper).toProductDTOs(any());

Perform the test with MockMVC perform(…) and Verify the test result with JsonPath and Hamcrest

  • Use MockMVC’s perform(...) to perform a mock HTTP test request with MockMvcRequestBuilders
  • Use JsonPath and Hamcrest to verify the JSON response data
// when + then
this.mockMvc.perform(get("/api/v1/products"))
    .andExpect(status().isOk())
    .andExpect(jsonPath("$[0].name", is(productDTO.getName())));

Implementation example

ProductAPITest.java

package com.hellokoding.springboot.restful.product;

import org.junit.Test;
import org.junit.runner.RunWith;
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.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doReturn;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@WebMvcTest
public class ProductAPITest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private ProductService productService;

    @MockBean
    private ProductMapper productMapper;

    @Test
    public void whenFindAll_thenReturnProductDTOList() throws Exception {
        // given
        ProductDTO productDTO = ProductDTO.builder()
            .name("P1")
            .description("P1 desc")
            .price(new BigDecimal("1"))
            .build();
        List<ProductDTO> productDTOs = Arrays.asList(productDTO);

        doReturn(new ArrayList<>()).when(productService).findAll();
        doReturn(productDTOs).when(productMapper).toProductDTOs(any());

        // when + then
        this.mockMvc.perform(get("/api/v1/products"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$[0].name", is(productDTO.getName())));
    }

    @Test
    public void whenFindById_thenReturnProductDTO() throws Exception {
        // given
        ProductDTO productDTO = ProductDTO.builder()
            .name("P1")
            .description("P1 desc")
            .price(new BigDecimal("1"))
            .build();

        doReturn(Optional.of(new Product())).when(productService).findById(anyLong());
        doReturn(productDTO).when(productMapper).toProductDTO(any(Product.class));

        // when + then
        this.mockMvc.perform(get("/api/v1/products/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name", is(productDTO.getName())));
    }
}

Source code

https://github.com/hellokoding/hellokoding-courses/tree/master/springboot-examples/springboot-restapi-testing-all-layers

Follow HelloKoding