TEST/JUnit

단위 테스트 방법 [part 1 : 컨트롤러(Controller)]

블로그 주인장 2023. 11. 8.

적합한 테스트 가이드

레이어(layer)별로 사용하기 적합한 방식의 테스트 가이드에 대해 알아보겠습니다.

그 중에 '컨트롤러 객체'를 테스트하는 방법을 알아보겠습니다.


컨트롤러 객체의 테스트


  1. 컨트롤러는 클라이언트로부터 요청을 받아 요청에 걸맞는 서비스 컴포넌트로 요청을 전달한다.
  2. 그 결과값을 가공해서 클라이언트에게 응답하는 역할을 수행한다.
  3. 즉, 애플리케이션을 구성하는 여러 레이어 중에 가장 웹에 가까이에 있는 모듈이라고 볼 수 있다.

 

ProductController 메서드

package com.springboot.test.controller;

import com.springboot.test.data.dto.ChangeProductNameDto;
import com.springboot.test.data.dto.ProductDto;
import com.springboot.test.data.dto.ProductResponseDto;
import com.springboot.test.data.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/product")
public class ProductController {

    private final ProductService productService;

    @Autowired
    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping()
    public ResponseEntity<ProductResponseDto> getProduct(Long number) {
        ProductResponseDto responseDto = this.productService.getProduct(number);

        return ResponseEntity.status(HttpStatus.OK).body(responseDto);
    }

    //생략...
}
  • ProductController 는 ProductService의 객체를 의존성 주입받는다.
  • 테스트를 하는 입장에서 ProductController만 테스트하고 싶다면 ProductService는 외부요인에 해당된다.
  • 독립적인 테스트 코드를 작성하기 위해서는 Mock 객체를 활용해야한다.

 

ProductController 메서드 테스트

@WebMvcTest(ProductController.class)
class ProductControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    ProductServiceImpl productService;

    @Test
    @DisplayName("MockMvc를 통한 Product Data 가져오기")
    void getProductTest() throws Exception {
        //given
        given(this.productService.getProduct(123L)).willReturn(
                new ProductResponseDto(123L, "pen", 5000, 2000));

        String productId = "123";

        //when
        mockMvc.perform(get("/product?number=" + productId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.number").exists())
                .andExpect(jsonPath("$.name").exists())
                .andExpect(jsonPath("$.price").exists())
                .andExpect(jsonPath("$.stock").exists())
                .andDo(print());
                
        //then
        verify(this.productService).getProduct(123L);
    }

 

 

테스트 어노테이션

1️⃣ @WebMvcTest(테스트 대상 클래스.class)

  • 웹에서 사용되는 요청과 응답에 대한 테스트를 수행할 수 있다.
  • 대상 클래스만 로드해 테스트를 수행한다.
  • 만약 대상 클래스를 추가하지 않으면 @Controller, @RestController, @ControllerAdvice 등의 컨트롤러 관련 빈 객체가 모두 로드된다.
  • @SpringBootTest 보다 가볍게 테스트하기 위해 사용된다.
  • 일반적으로 @WebMvcTest 어노테이션을 사용한 테스트는 슬라이스(Slice) 테스트라고 부른다.
  • 슬라이스 테스트
    1. 단위 테스트와 통합 테스트의 중간 개념으로 이해하면된다.
    2. 레이어드 아키텍처를 기준으로 각 레이어별로 나누어 테스트를 진행한다는 의미이다.
    3. 모든 외부 요인을 차단하고 테스트를 진행해야하지만, 컨트롤러는 개념상 웹과 맞닿은 레이어로써 외부 요인을 차단하고 테스트하면 의미가 없기 때문에 슬라이스 테스트를 진행하는 경우가 많다.

2️⃣ @MockBean

  • 실제 빈 객체가 아닌 Mock(가짜) 객체를 생성해서 주입하는 역할을 수행한다.
    • ex) ProductController가 의존성을 가지던 ProductService의 객체에 Mock 객체를 주입했다.
  • @MockBean이 선언된 객체는 실제 객체가 아니기 때문에 실제 행위를 수행하지 않는다.
  • 해당 객체는 개발자의 Mockito의 given() 메서드를 통해 동작을 정의해야한다.
    • given() : 어떤 객체에서 어떤 메서드가 호출되고 어떤 파라미터를 주입받는 지 가정한다.
    • willReturn() : 어떤 결과를 리턴할 것인지 정의하는 구조로 작성한다.
    • 메서드의 이름에서 알 수 있듯이 이부분의 코드가 앞에 설명한 Given에 해당한다.

3️⃣ @Test

  • 테스트 코드가 포함되어 있다고 선언하는 어노테이션이다.
  • JUnit Jupiter에서는 이 어노테이션을 감지하여 테스트 계획에 포함시킨다.

4️⃣ @DisplayName

  • 테스트 메서드의 이름이 복잡해서 가독성이 떨어지는 경우, 이 어노테이션을 통해 테스트에 대한 표현을 정의할 수 있다.

5️⃣ 그 외의 구성

  1. MockMvc
    • 컨트롤러의 API를 테스트하기 위해 사용된 객체이다.
    • Mock(가짜) 객체를 사용해 Spring MVC 동작을 재현할 수 있는 테스트 프레임워크
  2. perform() 메서드
    • 서버로 URL 요청을 보내는 것처럼 통신 테스트 코드를 작성해서 컨트롤러를 테스트할 수 있다.
    • MockMvcRequestBuilders에서 제공하는 HTTP 메서드로 URL을 정의해서 사용한다.
    • MockMvcRequestBuilders는 GET, POST, PUT, DELETE에 매핑되는 메서드를 제공한다.
    • MockMvcServletRequestBuilder 객체를 리턴하며, HTTP 요청 정보를 설정할 수 있게 된다.
    • 결과값으로 ResultActions 객체가 리턴되는데, andExpect() 메서드를 활용하여 결과값을 검증할 수 있다.
  3. andExpect() 메서드
    • ResultMatcher를 활용한다.
    • MockMvcResultMatchers 클래스에 정의되어 있는 메서드를 활용해 생성할 수 있다.
  4. andDo() 메서드
    • MockMvc 코드는 모두 합쳐져 있어 구분하기는 애매하다.
    • 하지만, 전체적인 'When-Then' 구조를 갖추고 있음을 확인할 수 있다.
  5. verify() 메서드
    • 지정된 메서드가 실행되었는지 검증하는 역할이다.
    • 일반적으로 given()에 정의된 동작과 대응한다.
반응형

댓글