[Spring 테스트] MockMvc란 무엇일까?

2022년 06월 24일

TOC

테스트 - MockMvc

1. MockMvc란?

스프링 프레임워크는 3.2버전 이후로 Spring MVC를 Mocking하여 웹 애플리케이션을 테스트 하는 MockMVC를 제공하고 있다.

MockMvc는 Spring MVC Test Framework이라고도 불리며 웹 애플리케이션을 실제 서버(서블릿 컨테이너)의 실행 없이 가상 환경에서 테스트 Mock request를 통해 Spring MVC 애플리케이션의 테스트를 할 수 있다. 실제 환경을 구축하지 않아도 되어 테스트가 가벼운편이며 Spring MVC 애플리케이션의 대부분의 기능들을 테스트 할 수 있다.

WebTestClient로 MockMvc를 대체할 수 있다.

2. Setup

2.1. MockMvc객체 생성

MockMvc를 설정하는 방법은 세 가지 방법이 있다.

  • 방법1. 수동으로 하나 이상의 컨트롤러를 MockMvc로 만들려는 경우 아래와 같이 standaloneSetup()메서드를 사용하면 된다.

    class MyWebTests {
    
      MockMvc mockMvc;
    
      @BeforeEach
      void setup() {
          this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
      }
      // ...
    }
  • 방법2. 스프링이 로드한 WebApplicationContext를 통해 MockMvc를 만들려는 경우 webAppContextSetup() 메서드를 사용하면 된다.

    class MyWebTests {
    
      MockMvc mockMvc;
    
      @BeforeEach
      void setup(WebApplicationContext wac) {
          this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
      }
    
      // ...
    
    }

둘의 차이는 standaloneSetup()의 경우 테스트 할 컨트롤러를 수동으로 초기화하고 주입하여야 하지만 webAppContextSetup()은 스프링이 로드한 WebApplicationContext로 쉽게 만들 수 있다는 점이다. 또한 standaloneSetup()은 테스트 하는 용도로 특정 컨트롤러만을 사용하겠다는 점에서 단위 테스트와 유사한 느낌을 주지만 webAppContextSetup()은 스프링 컨트롤러들과 그들의 의존성까지 모두 로드하기 때문에 통합 테스트의 느낌을 준다.

둘 중에서는 웬만하면 webAppContextSetup()를 사용하는 것을 추천한다. 겉으로 보기에는 standaloneSetup()가 테스트 할 컨트롤러만을 주입하여 만들기에 더 빠르다 생각될 수 있지만 스프링 테스트는 기본적으로 TestContext를 캐싱하기 때문에 webAppContextSetup()를 사용하여도 첫 테스트에만 해당 빈들을 불러오고 그 이후부터는 캐싱된 TestContext를 사용하여 속도 측면에서도 뒤떨어지지 않는다.

  • 방법3. @AutoConfigureMockMvc를 테스트 클래스 위에 붙여주고 @Autowired를 할 경우 MockMvc객체가 자동 주입됩니다.
@SpringBootTest
@AutoConfigureMockMvc
class MyWebTests {

    @Autowired
    private MockMvc mockMvc;
}

2.2. 필터 추가하기

addFilters() 메서드를 사용하면 필터 추가가 가능하다.

class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup() {
        this.mockMvc = MockMvcBuilders
                .standaloneSetup(new AccountController())
                .addFilters(new SessionFilters())
                .build();
    }
    // ...
}

3. 요청 보내기

mockMvc에서 요청에 대한 설정은 모두 perform()메서드 안에 들어가게 됩니다.

perform

perform()메서드의 파라미터 타입을 보면 RequestBuilder인 것을 확인할 수 있습니다. 우리는 perform 안에 빌더 패턴을 사용해 request에 대한 정보를 하나씩 채워가면 됩니다.

3.1. 요청 메서드 및 query parameter 설정하기

MockMvcRequestBuilders클래스에서는 요청 메서드로 get(), post(), put(), delete(), petch() 등 http request들을 모두 지원하고 있습니다. 우리는 상황에 맞는 요청 값을 불러와 아래와 같이 사용하면 됩니다.

mockMvc.perform(get("stations"));

url에 query parameter가 존재하는 경우에는 파라미터를 설정하는 2가지 방법이 존재합니다.

첫번째 방법은 요청 메서드 안에 2번째 이후 파라미터로 두는 방법입니다.

mockMvc.perform(get("/stations?name={name}", "line1"));

두번째 방법은 빌터 패턴을 통해 param()메서드로 파라미터를 설정하는 방법이 있습니다.

mockMvc.perform(get("/stations").param("name", "line1"));

3.2. body값 담아주기

request에 body값이 필요하다면 content()contentType()을 통해 담아줄 수 있다.

// StationRequest request = new StationRequest("강남역");

mockMvc.perform(post("/stations")
        .content(objectMapper.writeValueAsString(request))
        .contentType(MediaType.APPLICATION_JSON_VALUE)
        )

3.3. 요청 설정 메서드

MockMvc는 request를 설정하는 많은 매서드들을 제공하고 있다.

  • param / params : 쿼리 스트링 설정
  • cookie : 쿠키 설정
  • requestAttr : 요청 스코프 객체 설정
  • sessionAttr : 세션 스코프 객체 설정
  • content : 요청 본문 설정
  • contentType : 본문 타입 설정
  • header / headers : 요청 헤더 설정

자세한 메서드의 사용법을 알고 싶다면 해당 메서드들을 제공해주는 MockHttpServletRequestBuilder클래스를 살펴보기 바란다.

3.4. 사용 예시

@SpringBootTest
class StationControllerTest {

    MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp(WebApplicationContext context) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }

    @Test
    void createStation() throws Exception {
        StationRequest request = new StationRequest("강남역");

        mockMvc.perform(post("/stations")
                        .cookie(new Cookie("쿠키 이름", "쿠키 값"))
                        .header("헤더 이름", "헤어 값")
                        .content(objectMapper.writeValueAsString(request))
                        .contentType(MediaType.APPLICATION_JSON_VALUE));
    }
}

4. 검증하기

Response를 검증을 하기 위해서는 요청을 보낸 perform()뒤에 빌더 패턴을 사용하여 andExpect(..), andExpectAll(..)들을 붙여서 검증해야합니다. 빌더패턴을 사용하는 만큼 검증 메서드가 여러개가 붙을 수 있으며 해당 메서드들을 모두 통과해야지 검증이 통과하게 된다. 만약 하나라도 검증 실패할 경우 해당 테스트는 실패하게 된다.

  • andExpect(..) : 하나의 값을 검증하기 위해 사용한다.

    mockMvc.perform(get("/accounts/1"))
    			.andExpect(status().isOk());
  • andExpectAll(..) : 내부에 모든 값들이 통과되어야 한다.

    mockMvc.perform(get("/accounts/1")).andExpectAll(
      status().isOk(),
      content().contentType("application/json;charset=UTF-8"));

4.1. 검증 메서드

MockMvc에서 제공하는 검증 메서드는 아래의 메서드들이 있다.

  • status : 상태 코드 검증
  • header : 응답 header 검증
  • content : 응답 본문 검증
  • cookie : 쿠키 상태 검증
  • view : 컨트롤러가 반환한 뷰 이름 검증
  • jsonPath: body의 특정 값을 검증.
  • redirectedUrl(Pattern) : 리다이렉트 대상의 경로 검증
  • model : 스프링 MVC 모델 상태 검증
  • request : 세션 스코프, 비동기 처리, 요청 스코프 상태 검증
  • forwardedUrl : 이동대상의 경로 검증
  • model().atrribute("userId", "fly123")

자세한 메서드의 사용법을 알고 싶다면 해당 메서드들을 제공해주는 MockMvcResultMatchers클래스를 살펴보기 바란다.

@SpringBootTest
class StationControllerTest {

    MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp(WebApplicationContext context) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }

    @Test
    void createStation() throws Exception {
        StationRequest request = new StationRequest("강남역");

        mockMvc.perform(post("/stations")
                        .content(objectMapper.writeValueAsString(request))
                        .contentType(MediaType.APPLICATION_JSON_VALUE))
                .andExpect(status().isCreated())
                .andExpect(header().string("Location", "/stations/1"))
                .andExpect(jsonPath("$.name").value("강남역"));
    }
}

4.2. 사용 예시

@SpringBootTest
class StationControllerTest {

    MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp(WebApplicationContext context) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }

    @Test
    void createStation() throws Exception {
        StationRequest request = new StationRequest("강남역");

        mockMvc.perform(post("/stations")
                        .content(objectMapper.writeValueAsString(request))
                        .contentType(MediaType.APPLICATION_JSON_VALUE))
                .andExpect(status().isCreated())
                .andExpect(header().string("Location", "/stations/1"))
                .andExpect(jsonPath("$.name").value("강남역"));
    }
}

5. 기타 기능

5.1. print()

print()메서드는 테스트를 하며 만들어지는 MockMvcResultHandlers에서 수행된 요청의 결과를 가져와 출력해주는 일을 한다. 해당 메서드를 사용하면 우리는 테스트에 사용된 request, response에 대한 자세한 정보들을 콘솔창에서 확인할 수 있다.

@SpringBootTest
class StationControllerTest {

    MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp(WebApplicationContext context){
        this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }

    @Test
    void createStation() throws Exception {
        StationRequest request = new StationRequest("강남역");

        mockMvc.perform(post("/stations")
                .content(objectMapper.writeValueAsString(request))
                .contentType(MediaType.APPLICATION_JSON_VALUE))
                .andDo(print())
                .andExpect(status().isCreated());
    }
}

andDo(print())를 추가한 결과 테스트를 수행하면 아래와 같이 request/response의 결과를 확인할 수 있다.

print

Reference

Buy me a coffeeBuy me a coffee
Written by

@Seongwon

기술공유를 통해 새로운 가치 창조을 추구하는 백엔드 개발자 오성원입니다.
©SeongwonOh