MockMvc 넌 또 뭐하는 애야?

Panda·2022년 3월 25일
1

Spring

목록 보기
14/42

MockMvc에 대해서 한번 공부해보려고 합니다.

MockMvc 그게 뭔데?

MockMvc는 웹 어플리케이션을 애플리케이션 서버에 배포하지 않고 테스트용 MVC환경을 만들어 요청 및 전송, 응답기능을 제공해주는 유틸리티 클래스라고 합니다.
(말 그대로 진짜 MockMvc 인 것입니다.)

즉, 실제 객체와 비슷하지만 테스트에 필요한 기능만 가지는 가짜 객체를 만들어서 애플리케이션 서버에 배포하지 않고도(실제 서블릿 컨테이너를 사용하지 않고) 스프링 MVC 동작을 재현할 수 있는 클래스를 의미합니다.

MockMvc 필요성

MockMvc 필요성이기도 하지만 Mock 필요성에 대해 말을 하자면

객체를 테스트하려면 테스트 대상 객체가 메모리에 있어야 하는데 생성하는 데 복잡한 절차가 필요하거나 많은 시간 이 소요되는 객체가(ex. 서비스레이어) 있을 수 있고, 웹 어플리케이션의 Controller처럼 WAS나 다른 소프트웨어의 도움이 반드시 필요한 객체도 있을 수 있습니다.

-> 즉 실제 객체를 테스트 하려면 준비 과정이라던지 라이브러리 주입 등 비용과 시간이 많이 들게 됩니다.
이때 Mock 은 이러한 문제를 해결해 더욱 빠른 테스트를 진행할 수 있습니다.

필요 기능

  • 이러한 복잡한 객체를 테스트하기 위해 실제 객체와 비슷한 가짜 객체를 만들어 테스트에 필요한 기능만 기지도록 모킹하면 테스트가 쉬워집니다.

의존성

  • 또한 복잡한 의존성을 가지고 있을 때, 모킹한 객체를 이용하면 의존성을 단절시킬 수 있어 쉽게 테스트할 수 있습니다.
    웹 애플리케이션에서 Controller를 테스트할 때, 서블릿 컨테이너를 모킹하려면 @WebMvcTest 또는 @AutoConfigureMockMvc를 사용합니다.

@WebMvcTest @AutoConfigureMockMvc 차이점

  • @WebMvcTest
    • 웹에서 테스트하기 힘든 컨트롤러를 테스트 하는데 적합
      -@Controller, @ControllerAdvice등을 사용 가능
    • @Service, @Component, @Repository 등은 사용 불가
  • @AutoConfigureMockMvc
    • 컨트롤러 뿐만 아니라 테스트 대상이 아닌 @Service@Repository 붙은 객체들도 모두 메모리에 올립니다.
    • MockMvc를 보다 세밀하게 제어하기 위해 사용하며, 전체 애플리케이션 구성을 로드하고 MockMvc를 사용하려는 경우 @WebMvcTest보다
      @AutoConfigureMockMvc + @SpringBootTest를 고려해야 합니다.

따라서 정말 심플하게 테스트를 수행할 거면 @WebMvcTest
세밀하게 고려할거면 @AutoConfigureMockMvc 를 사용하면 될 것 같습니다.

MockMvc 쓰는법

테스트 기본 형태

@SpringBootTest
@Transactional
@AutoConfigureMockMvc
class CategoryControllerTest {
    @Autowired
    private MockMvc mockMvc;

    private final String baseUrl = "/api/v1/category";

GET /api/v1/category 호출 시 반환되는 Json Response

{
  "data" : [
    {
    	"id" : 23,
    	"name" : "음식"
  	}
  ],
  "code" : "FIND_ALL",
  "message" : "카테고리 조회 성공"
}

Json Response 테스트

@Test
public void 전체조회() throws Exception {
	// when then
	mockMvc.perform(get(baseUrl)
    	.contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())		// status 200
        .andExpect(jsonPath("$.data").isArray())	// 배열인지 
        .andExpect(jsonPath("$.data").isNotEmpty())	// 배열이 안비었는지
        .andExpect(jsonPath("$.code").value("FIND_ALL"))	// 값 비교
        .andExpect(jsonPath("$.message").value("카테고리 조회 성공"))	// 값 비교
        .andDo(print());	// 출력
}

JsonPath에 해당하는 값이 존재하는지

mockMvc.perform(get(baseUrl))
	.andExpect(jsonPath("$.data.id").exists())

JsonPath에 값이 존재하지 않는지

mockMvc.perform(get(baseUrl))
    .andExpect(jsonPath("$.data").doesNotExist());	// null 인지 판별

등등 다양한 Matchers 가 존재함으로 필요한 Matcher가 있으면 따로 찾아서 적용하면 될 것 같습니다.

Request 설정 메소드

mockMvc.perform(get("test")
	.param("query", "부대찌개")
    .cookie("쿠키 값")
    .header("헤더 값")
    .contentType(MediaType.APPLICATION.JSON)
    .content("json으로"));
  • param / params : 쿼리 스트링 설정
  • cookie : 쿠키 설정
  • requestAttr : 요청 스코프 객체 설정
  • sessionAttr : 세션 스코프 객체 설정
  • content : 요청 본문 설정 (body)
  • contentType : 본문 타입 설정
  • header / headers : 요청 헤더 설정

Request, Response 설정 하는 부분이 () 이거 떄문에 헷갈릴수 있는데
통신 메소드 뒤에 바로 체인걸면 Request 설정이고
perform 메소드 뒤에 체인걸면 Reponse 설정 입니다.

Response 검증 메소드

mockMvc.perform(get("test"))
	.param("query", "부대찌개")
    .cooke("쿠키 값")
    .header("헤더 값")
    .contentType(MediaType.APPLICATION.JSON)
    .content("json으로")
    .andExpect(status().isOk())
    .andExpect(content().string("expect json값"))
    .andExpect(redirectedUrl("/foodList?name=부대찌개"));
    // .andExpect(view().string("뷰이름"));
  • status : 상태 코드 검증
  • header : 응답 header 검증
  • content : 응답 본문 검증
  • cookie : 쿠키 상태 검증
  • view : 컨트롤러가 반환한 뷰 이름 검증
  • redirectedUrl(Pattern) : 리다이렉트 대상의 경로 검증
  • model : 스프링 MVC 모델 상태 검증
  • request : 세션 스코프, 비동기 처리, 요청 스코프 상태 검증
  • forwardedUrl : 이동대상의 경로 검증

MockMvc 필터

MockMvc를 쓰다가 Response 에서 한글이 깨지는 경우가 발생을 하였다.
이때 테스트 코드 자체는 이상이 없습니다. 단순 출력에서 깨지는 거라서

MockMvc에 필터를 추가를 해줘서 한글 인코딩이 가능하게 바꿔보았습니다.

@SpringBootTest
@Transactional
@AutoConfigureMockMvc
class CategoryControllerTest {
    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext ctx;

    private final String baseUrl = "/api/v1/category";

    @BeforeEach
    private void setup() {
        // Response Content 에서 출력시 한글 깨짐 방지
        // 이 방법은 WebApplicationContext를 주입 받아야하므로 @SpringBootTest에서만 사용가능합니다.
        // 혹은 webAppContestSetup 대신에 standAloneSetup 으로 컨트롤러를 지정할수도있음
        this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx)
                .addFilters(new CharacterEncodingFilter("UTF-8", true))  // 필터 추가
                .alwaysDo(print())
                .build();
    }

이렇게 해주면 제대로 인코딩이 되어 한글이 잘 출력이 됩니다.

여기서 MockMvcBuilders.webAppContextSetup(ctx) 가 있고
MockMvcBuilders.standaloneSetup(controller) 가 있는데 살펴보자면

webAppContextSetup

  • 실제 Spring Mvc Configuration을 로딩합니다.
  • TestContext framework이 Spring configuration을 캐쉬하기 때문에, 많은 테스트가 있어도 빠르게 테스트를 수행할 수 있습니다.
  • 이때 주입받으려면 @SpringBootTest 또는 @WebAppConfiguration 가 필요합니다.
  • 통합 테스트 위주라고 보면 될것 같습니다.

standaloneSetup

  • Spring configuration을 로딩하지 않고 Controller instance를 수동으로 생성
  • 유닛 테스트 위주라고 보면 될 것 같습니다.

느낀 점

MockMvc를 쓰면서 코틀린 DSL 을 너무나도 쓰고싶다고 느꼈고 ㅋㅋㅋㅋㅋㅋ

TDD를 정말 제대로 하려면 진짜 많이 알아야한다고 느꼈습니다.

TDD도 공부해야하고
JVM도 해야하고
JPA도 해야하고
Spring도 해야하고
Gradle도 해야하고
Java 랑 Kotlin도 공부해야되는데...

알고리즘, 디자인 패턴 은 또 언제하지 ㅠㅠㅠㅠ

profile
실력있는 개발자가 되보자!

0개의 댓글