해결됨
Practical Testing: 실용적인 테스트 가이드
컨트롤러 테스트의 Mock을 통한 Stubbing 관련 질문
안녕하세요. 우빈님!먼저 우빈님의 테스트 강의가 너무나 재미있어, 테스트에 대한 막연한 지식을 구체화하고 테스트 작성 열망을 크게 키울 수 있어서 감사하다는 말씀을 드리고 싶습니다!다름이 아니라, Controller 테스트를 위해 @WebMvcTest를 통해 Service와 Repository를 Mocking하여 단위 테스트의 형식으로 작성한다는 것을 배웠습니다. 이렇게 배운 것을 사이드 프로젝트에 적용해보며, 의문점이 생겼는데 능력 부족으로 인해 의문이 해결되지 않아 질문을 드리려 합니다. 상황강의에서 작성한 컨트롤러 테스트 중 일부인 OrderControllerTest의 테스트 메서드는 다음과 같습니다.@DisplayName("신규 주문을 등록한다.")
@Test
void createOrder() throws Exception {
// given
OrderCreateRequest request = OrderCreateRequest.builder()
.productNumbers(List.of("001"))
.build();
// when & then
mockMvc.perform(
post("/api/v1/orders/new")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
)
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value("200"))
.andExpect(jsonPath("$.status").value("OK"))
.andExpect(jsonPath("$.message").value("OK"));
}또한, 해당 메서드의 실행 로그를 보면 MockHttpServletResponse이 다음과 같다는 것을 볼 수 있었습니다.MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"application/json"]
Content type = application/json
Body = {"code":200,"status":"OK","message":"OK","data":null}
Forwarded URL = null
Redirected URL = null
Cookies = [] 이후, 저의 사이드 프로젝트의 컨트롤러 테스트를 위와 동일한 방식으로 작성하였지만, 아래와 같이 MockHttpServletResponse의 Body가 빈 채로 응답이 되어 테스트가 실패하게 되는 문제가 발생했습니다.MockHttpServletResponse:
Status = 200
Error message = null
Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []이때, given()을 통해 Service에 대한 행위를 Stubbing해주니 정상적으로 테스트가 성공하는 것을 확인했습니다. 문제 파악을 위해 조사한 결과 추측되는 차이점은 다음과 같습니다.강사님의 프로젝트에서 Controller의 응답 타입은 커스텀 응답 객체인 ApiResponse를 사용합니다.저의 사이드 프로젝트에서 Controller의 응답 타입은 HttpEntity를 상속하는 ResponseEntity를 사용합니다. 질문Q1. 강사님의 코드를 보면, 아래와 같이 createOrder 메서드에 대한 Stubbing 없이도 정상적으로MockHttpServletResponse의 Body가 응답되어 테스트가 성공합니다.// given
...
given(orderService.createOrder(any()))
.willReturn(OrderResponse.builder()
... // 생략
.build()
);
// 없어도 테스트는 성공한다.저는 given() 절에 @MockBean을 통해 Mock 객체로 설정한 OrderService가 어떤 행위를 해야할지 Stubbing 해주어야 하는 것으로 이해하고 있었는데, 어떻게 Stubbing 없이 Body가 정상적으로 채워져 테스트가 성공한 것인지 궁금합니다. Q2. Q1과 연관하여 강사님의 코드에서는 ApiResponse라는 커스텀한 응답 객체를 컨트롤러 메서드의 응답으로 사용하는데, 제 사이드 프로젝트에서의 응답 타입은 ResponseEntity를 사용하고 있습니다. 이 차이 때문에 발생하는 문제인지 궁금합니다. Q3. 이번 의문점을 통해 컨트롤러 테스트에서 메서드의 행위에 대한 기댓값을 Stubbing하여 검증하는 것이 일종의 답정너(?)와 같은 테스트를 작성하는 것은 아닐까? 라는 생각과 함께, 컨트롤러 테스트 방식에 많은 고민을 해야 하겠다는 다짐을 하게 되었습니다. 이에 대한 우빈님의 생각은 어떠하신지 궁금합니다. 질문이 수준이 다소 떨어지지만, 이 의문점을 해결하고 싶은 마음에 장황하게 나열할 수 밖에 없었음을 양해 부탁드립니다.답변 기다리겠습니다. 감사합니다!