스프링과 MvcMock 테스트

들어가며

  • 스프링의 통합 테스트(@SpringBootTest)를 수행할 때 MockMVC를 주로 사용한다.
  • MockMvc에서 주로 사용하는 기능을 정리하고자 한다.
  • 참고로 MockMvc에 사용하는 정적 메서드가 많다. 해당 import를 찾는 과정이 복잡하므로 보통 아래의 코드를 긁어서 사용한다. 사실 차후 내가 사용하기 위한 기록으로서 지금 블로그를 작성한다(^^;).
  • 참고로 유사한 스태틱 메서드라 하더라도 Request와 Response 형태로 분리된다. 이 부분을 고려하여 테스트 코드를 작성하자!
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class RestControllerExceptionAdviceTest {

    @BeforeEach
    void init(WebApplicationContext ctx){
        this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx)
                .addFilters(new CharacterEncodingFilter("UTF-8", true))
                .build();
    }

    private MockMvc mockMvc;

    @Test
    void legacy_not_filtered() throws Exception {
        mockMvc.perform(get("/legacy/greeting"))
                .andExpect(status().isOk())
                .andExpect(content().string("{\"msg\":\"hello, legacy controller\"}"))
                .andDo(print());
    }

    @Test
    void no_authorization_header() throws Exception {
        mockMvc.perform(get("/rest/greeting"))
                .andDo(print())
                .andExpect(status().isUnauthorized())
                .andExpect(jsonPath("$.code", equalTo("UNAUTHORIZED")))
        ;
    }

    @Test
    void wrong_authorization_header1() throws Exception {
        mockMvc.perform(
                        get("/rest/greeting")
                        .header("Authorization", "bearer abc")
                )
                .andDo(print())
                .andExpect(status().isUnauthorized())
                .andExpect(jsonPath("$.code", equalTo("UNAUTHORIZED")))
        ;
    }

    @Test
    void wrong_authorization_header2() throws Exception {
        mockMvc.perform(
                        get("/rest/greeting")
                        .header("Authorization", "Bearer abc")
                )
                .andDo(print())
                .andExpect(status().isUnauthorized())
                .andExpect(jsonPath("$.code", equalTo("ACCESS_TOKEN_ERROR")))
                .andExpect(jsonPath("$.message", containsString("유효하지 않은")))
        ;
    }
}
  • 참고로 위는 access token을 테스트하기 위한 코드이다. 인터셉터와 ExceptionHandler 테스트 중에 있다.
  • html 렌더링에 대해서도 테스트하기 좋지만, 대체로 rest api에 대한 테스트를 진행할 것이라 생각한다. 이때 jsonPath를 잘 사용하는데 해당 표현식은 더 다양하다. 문자열 비교를 위한 스태틱 메서드도 다양한데, String.contains()를 기대하면 containsString 메서드를 사용한다.
  • body가 있을 경우 대략 아래와 같은 흐름이다.
mockMvc.perform(post("/greeting/")
				.content("{\"msg\":\"hello world!\"}")
				.contentType(MediaType.APPLICATION_JSON)
				.accept(MediaType.APPLICATION_JSON)
)