[Spring] 컨트롤러 객체 테스트

WOOK JONG KIM·2022년 10월 30일
0
post-thumbnail

컨트롤러는 클라이언트로 부터 요청을 받아 요청에 걸맞은 서비스 컴포넌트로 요청을 전달하고 그 결과값을 가공해서 클라이언트에게 응답을 하는 역할

컨트롤러의 getProduct 코드

private final ProductService productService;

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

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

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

ProductController는 ProductService의 객체를 의존성 주입 받음

테스트하는 입장에서 ProductController만 테스트 하고 싶다면 ProductService는 외부 요인에 해당
-> 독립적인 테스트 코드 작성을 위해서는 Mock 객체 활용 해야 함

@WebMvcTest(ProductController.class)
public class ProductControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    ProductServiceImpl productService;

    @Test
    @DisplayName("MockMvc를 통한 Product 데이터 가져오기 테스트")
    void getProductTest() throws Exception{
        given(productService.getProduct(123L)).willReturn(
                new ProductResponseDto(123L, "pen", 5000, 2000));

        String productId = "123";

        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());

        verify(productService).getProduct(123L);
    }
}
  • @WebMvcTest : 웹에서 사용되는 요청과 응답에 대한 테스트 수행가능. 인자 넣을 시 대상 클래스만 로드 해 테스트 수행하며, 추가 안할 시 @Controller,@RestController,@ControllerAdivce등의 컨트롤러 관련 빈 객체가 모두 로드됨
  • @MockBean : 실제 객체가 아닌 Mcok 객체를 생성해서 주입하는 역할 수행
    -> 행위를 수행하지 않아 개발자가 Mockito의 given() 메서드를 통해 동작 정의
  • @Test : 테스트 코드가 포함돼 있다고 선언하는 어노테이션, JUnit Jupiter에서 이 어노테이션을 감지하여 테스트 계획에 포함 시킴
  • @DisplayName : 테스트에 대한 표현을 정의 가능함(메서드 이름 복잡할 시 주로 사용)

@WebMvcTest 어노테이션을 사용하는 테스트는 슬라이스 테스트라 부름

단위 테스트와 통합 테스트의 중간 개념으로 , 레이어드 아키텍처를 기준으로 각 레이어별로 나누어 테스트 진행하는 의미

단위 테스트를 수행하기 위해서는 모든 외부 요인을 차단하고 테스트를 진행해야 하지만 컨트롤러는 개념상 웹과 맞닿은 레이어로서 외부 요인을 차단하고 테스트하면 의미가 없기에 슬라이스 테스트로 많이 진행

@MockBean 어노테이션을 통해 ProductController가 의존성을 가지고 있던 ProductService 객체에 Mock 객체 주입

@MockBean은 스프링 컨텍스트에 mock객체를 등록하게 되고 스프링 컨텍스트에 의해 @Autowired가 동작할 때 등록된 mock객체를 사용할 수 있도록 동작

given() 메서드를 통해 이 객체에서 어떤 메서드가 호출되고 어떤 파라미터가 주입받는지 가정 후 willReturn()를 통해 어떤 결과를 리턴할 것인지 정의하는 구조로 코드 작성

MockMvc는 컨트롤러의 API를 테스트하기 위해 사용하는 객체
-> 서블릿 컨테이너의 구동 없이 가상 MVC 환경에서 모의 HTTP 서블릿을 요청하는 유틸리티 클래스

perform() 메서드를 이용하면 서버로 URL 요청을 보내는 것처럼 통신 테스트 코드를 작성해서 컨트롤러를 테스트 할 수 있음
-> MockMvcRequestBuilders에서 제공하는 HTTP 메서드로 GET,POST,PUT,DELETE에 매핑되는 메서드 제공하며 MockHttpServletRequestBuilder 객체를 리턴

perform의 결과값은 andExpect()메서드를 사용해 결과값 검증을 수행

andExpect 안에서 MockMvcResultmatcher의 메서드들을 활용

결과 확인을 위해서는 andDo() 메서드 사용

verify()메서드는 지정된 메서드가 실행되었는지 검증하는 역할


컨트롤러의 createProduct() 메서드 코드

@PostMapping()
    public ResponseEntity<ProductResponseDto> createProduct(@RequestBody ProductDto productDto){
        ProductResponseDto productResponseDto = productService.saveProduct(productDto);

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

@RequestBody로 값을 받음

void createProductTest() throws Exception{
        given(productService.saveProduct(new ProductDto("pen",5000,2000)))
                .willReturn(new ProductResponseDto(12315L,"pen",5000,2000));

        ProductDto productDto = ProductDto.builder()
                .name("pen")
                .price(5000)
                .stock(2000)
                .build();

        Gson gson = new Gson();
        String content = gson.toJson(productDto);
        
        mockMvc.perform(
                post("/product")
                        .content(content)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.number").exists())
                .andExpect(jsonPath("$.name").exists())
                .andExpect(jsonPath("$.price").exists())
                .andExpect(jsonPath("$.stock").exists())
                .andDo(print());
        
        verify(productService).saveProduct(new ProductDto("pen", 5000, 2000));
    }

Gson은 구글에서 개발한 JSON 파싱 라이브러리로서 자바 객체를 JSON 문자열로 변환하거나 JSON 문자열을 자바 객체로 변환하는 역할을 함

-> pom.xml에 의존성 추가해야함

<dependency>
			<groupId>com.google.code.gson</groupId>
			<artifactId>gson</artifactId>
</dependency>
  1. given을 통해 동작 규칙 정함
  2. 테스트에 필요한 객체 생성
  3. 리소스 생성 기능을 테스트하기에 post메서드를 통해 url 구성
  4. @RequestBody의 값을 넘겨주기 위해 content() 메서드에 DTO의 값을 담아 테스트 진행
  5. POST 요청을 통해 도출된 결과값에 각 항목이 존재하는지 jsonPath.exists()를 통해 검증
profile
Journey for Backend Developer

0개의 댓글