제육's 휘발성 코딩
반응형

컨트롤러 테스트

Mocking

  • 테스트를 위해 실제 객체를 사용하는 것처럼 테스트를 위해 만든 모형(가짜 객체)를 만들어서 사용하는 것을 Mocking이라고 한다. 모킹한 객체를 이용하면 의존성을 단절시킬 수 있어 테스트하기 좋다.
  • Mocking은 @MockMvc를 주입 받아 사용하며, 같이 사용하는 어노테이션으로는 @WebMvcTest@AutoConfigureMockMvc가 있다.

MockMvc

perform()

  • 해당 메서드를 사용하면, 브라우저에서 서버에 URL을 요청(GET, POST 등)하듯 컨트롤러를 실행시킬 수 있다. 요청을 통해 MockMvcHttpServletRequestBuilder 객체를 리턴하면서 HTTP 요청 프로토콜에 관련 정보(파라미터, 헤더, 쿠키 등)을 설정할 수 있다.
  • RequestBuilder 객체를 인자로 받는데, 해당 객체는 MockMvcRequestBuilder의 정적 메서드를 이용해서 생성한다. 즉, MockMvcHttpServletRequestBuilderMockMvcRequestBuilder를 다시 리턴하기 때문에 복잡한 체이닝을 구성하여 요청할 수 있다.

andExpect()

  • perform()메서드는 결과로 ResultAction 객체를 리턴하는 데, 해당 결과를 검증할 수 있도록 andExpect()를 제공한다.
  • andExpect()ResultMatcher 객체를 요구하는데 MockMvcResultMatchers에 정의된 정적 메서드를 통해 생성할 수 있다.
  • status(), view(), model(), andDo() 등의 메서드를 제공한다.
status() : 응답 상태 코드 검증

image

view() : 뷰 / 리다이렉트 검증
andExpect(view().name("hello")) // 리턴한 뷰가 hello 인지 검증
andExpect(redirectUrl("/index")) // index 화면으로 리다이렉트 했는지 검증
  • 다음과 같이 컨트롤러가 리턴하를 뷰를 검증하는 용도로 사용
model() : 모델 정보 검증
  • 컨트롤러에 저장한 모델의 정보들을 검증할 경우 MockMvcResultMatchers.model() 메서드를 사용한다.
  • attributeExists(String name : name에 해당하는 데이터가 Model에 있는지 검증
  • attribute(String name, Object value) : name에 해당하는 데이터가 value 객체인지 검증
andDo() : 요청/응답 전체 메시지 확인
  • 실제로 생성된 요청과 응답 메시지를 모두 확인하고 싶은 경우에 perform() 메서드가 리턴하는 ResultActionsandDo(ResultHandler handler) 메서드를 사용한다.
  • andDo(print()) 를 통해 콘솔에 요청 / 응답에 관련된 정보를 모두 출력할 수 있다.

@WebMvcTest

https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration

  • MVC를 위한 테스트로 웹에서 테스트하기 힘든 컨트롤러를 테스트하는 용도로 사용
  • @SpringBootTest 어노테이션 보다 가볍게 테스트할 수 있다.
  • @Controller, @ControllerAdvice, @JsonComponent, Converter 등 만 스캔하도록 제한

@AutoConfigureMockMvc

image

  • @WebMvcTest@SpringBootTestMockMvc를 모킹하기 때문에 충돌이 발생하여 같이 사용할 수 없다.

사용 예시

@Test
@DisplayName("관리자는 컨텐츠 생성 시 201 응답을 받는다.")
fun createContentSuccessTest() {
  val createCategory = adminCategoryService.createCategory(CreateCategoryDto("카테고리", "카테고리 설명"))
  val categoryId = createCategory.id
  val createContentDto = objectMapper.writeValueAsString(
    CreateContentDto(
      "제목",
      LocalDateTime.now(),
      LocalDateTime.now(),
      "https://videoUrl.com",
      "https://imgUrl.com",
      false
    )
  )
  mockMvc.perform(
    post("/admin/categories/$categoryId/contents")
    .content(createContentDto)
    .contentType(MediaType.APPLICATION_JSON)
  )
  .andExpect(status().`is`(201))
  .andExpect(jsonPath("$..['id']").exists())
  .andExpect(jsonPath("$..['categoryId']").exists())
  .andExpect(jsonPath("$..['displayStartAt']").exists())
  .andExpect(jsonPath("$..['displayEndAt']").exists())
  .andExpect(jsonPath("$..['imgUrl']").exists())
  .andExpect(jsonPath("$..['videoUrl']").exists())
  .andExpect(jsonPath("$..['visible']").exists())
  .andDo(print())
}

참고 자료

반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^