베오
DCode
베오
전체 방문자
오늘
어제
  • 분류 전체보기 (218)
    • 공지사항 (1)
    • 잡설 (1)
    • Programming (33)
      • [C] (1)
      • [Java] (4)
      • [Python] (2)
      • [Android] (2)
      • [Network] (0)
      • [Operation System] (2)
      • [Spring Boot] (22)
      • [Docker] (0)
    • Algorithm (31)
      • 자료구조 (2)
      • 알고리즘 (Java) (14)
      • 알고리즘 (기초) (15)
    • Coding Test (131)
      • BOJ (131)
      • Algospat (0)
    • 이론적인거 (14)
      • 보안 (5)
      • 오류 해결 (2)
      • 디자인 패턴 (5)
      • 네트워크 (1)
      • 기타 (1)
    • 최신기술 (4)
      • 블록체인 (1)
    • [Project] (1)

블로그 메뉴

  • 🐈‍⬛ GitHub
  • 📫 방명록
  • 🔖 태그

공지사항

인기 글

티스토리

hELLO · Designed By 정상우.
베오

DCode

Programming/[Spring Boot]

컨트롤러 테스트 코드 작성

2023. 3. 16. 10:25

Slice Test

@WebMvcTest Annotation을 사용한 테스트를 Slice Test라고 한다.

단위 테스트와 통합 테스트의 중간 개념

레이어드 아키텍처를 기준으로 각 레이어별로 나누어 테스트를 진행한다는 의미


개요

의존성 주입을 받는 클래스의 경우 의존성 주입을 받는 객체는 외부 요인에 해당된다.

이를 최대한 배제하기 위해서 Mock을 사용한다.

@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 productResponseDTO = productService.getProduct(number);

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

위 코드에서 productService는 의존성 주입을 받는다.

하지만 이는 외부 요인에 해당되므로 이를 Mock를 이용해서 테스트 처리해보자


getProductTest 메서드 구현

Mock을 이용하여 작성한 테스트코드는 다음과 같다.

package com.springboot.test.controller;

import com.springboot.test.data.dto.ProductResponseDTO;
import com.springboot.test.service.impl.ProductServiceImpl;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.Mockito.verify;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@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);
    }
}

@MvcTest(테스트 대상 클래스.class)

  • 웹에서 사요되는 요청과 응답에 대한 테스트를 수행할 수 있다.
  • 대상 클래스만 로드하여 테스트를 수행한다.
  • 추가하지 않으면 컨트롤러 관련 빈 객체가 모두 로드된다.

@MockBean

  • 가짜 객체를 생성해서 주입해주는 역할
  • 실제 행위를 수행하지는 않는다.

    → Mockito의 given() 메서드를 통해 동작을 정의해야 한다.

@Test

  • 테스트 코드임을 알리는 Annotation

@DisplayName

  • 테스트 메서드 이름이 복잡해서 가독성이 떨어질 경우 이를 통해 표현을 정의할 수 있다.

getProductTest 메서드 코드 설명

@MockBean
ProductServiceImpl productService;

위 코드는 ProductController가 의존성을 가지고 있던 ProductService 객체에 Mock 객체를 주입한다는 의미이다.

Mock 객체는 껍데기므로 어떤식으로 동작하는지 정의해야한다.

given(productService.getProduct(123L)).willReturn(
                new ProductResponseDTO(123L, "pen", 5000, 2000));

위 코드는 실제로 동작하는 방식을 정의한 것이다.

  • given()
    • 이 객체에서 어떤 메서드가 호출되고 어떤 파라미터를 주입받는지 가정
  • willReturn()
    • 어떤 결과를 리턴하는지 정의

이제 실제로 검증하는 코드를 살펴보자

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());
  • perform()
    • 서버로 URL 요청을 보내는 것 처럼 테스트 코드 작성 가능
    • get 요청을 위 url로 보낸다는 의미이다.
    • ResultActions 객체가 리턴
  • andExpect()
    • 결괏값 검증을 수행할 수 있다.
    • status 가 ok인가?
    • number 속성이 존재하는가?
    • name 속성이 존재하는가?
    • price 속성이 존재하는가?
    • stock 속성이 존재하는가?
  • andDo()
    • 요청과 응답의 전체 내용을 확인하는 메서드

지정된 메서드가 실행됐는지 검증하는 코드를 살펴보자

verify(productService).getProduct(123L);

해당 코드는 지정된 메서드가 실행됐는지 확인하는 코드이다.


createProductTest 메서드 구현

Mock를 이용하여 createProcut를 테스트하는 코드는 다음과 같다.

package com.springboot.test.controller;

import com.google.gson.Gson;
import com.springboot.test.data.dto.ProductDTO;
import com.springboot.test.data.dto.ProductResponseDTO;
import com.springboot.test.service.impl.ProductServiceImpl;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.Mockito.verify;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(ProductController.class)
public class ProductControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    ProductServiceImpl productService;

    @Test
    @DisplayName("Product 데이터 생성 테스트")
    void createProductTest() throws Exception {
        //Mock 객체에서 특정 메서드가 실행되는 경우 실제 Return을 줄 수 없기 때문에
        // 가정 사항을 만들어준다.
        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 문자열을 객체로 변환하는 역할을 한다.


ProductDTO

createProductTest에서 쓰인 ProductDTO 코드는 다음과 같다.

package com.springboot.test.data.dto;

import lombok.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class ProductDTO {

    private String name;
    private int price;
    private int stock;
}

createProductTest 메서드 코드 설명

아래 코드는 saveProduct 호출 시 어떻게 동작할지 정의하는 것이다.

given(productService.saveProduct(new ProductDTO("pen", 5000, 2000)))
        .willReturn(new ProductResponseDTO(12315L, "pen", 5000, 2000));

ProductDTO 객체를 입력받으면 → ProductResponseDTO 객체를 반환한다고 정의한다.

아래 코드는 ProductDTO 객체를 만든 뒤, GSON을 이용하여 JSON화 시킨 것이다.

ProductDTO productDTO = ProductDTO.builder()
                .name("pen")
                .price(5000)
                .stock(2000)
                .build();

        Gson gson = new Gson();
        String content = gson.toJson(productDTO);

아래 코드는 post 요청을 보내는 방법

보냈을 때 예측 결과를 확인하는 것이다.

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

content는 위에서 정의한 ProductDTO를 JSON화 시킨 값을 전달하고

전달 타입은 JSON으로 정의한다.

요청이 되면 응답으로 200 ok응답이 와야하며

number 속성, name 속성, price 속성, stock 속성이 존재해야한다.

아래 코드는 실제 위 메소드가 정상적으로 동작했는지 확인한다.

verify(productService).saveProduct(new ProductDTO("pen", 5000, 2000));


Uploaded by N2T

'Programming > [Spring Boot]' 카테고리의 다른 글

테스트 주도 개발(TDD)  (0) 2023.03.16
테스트코드 작성 (이론편)  (0) 2023.03.16
Gson  (0) 2023.03.16
서비스 테스트 코드 작성  (0) 2023.03.16
리포지토리 테스트 코드 작성  (0) 2023.03.16
    'Programming/[Spring Boot]' 카테고리의 다른 글
    • 테스트 주도 개발(TDD)
    • 테스트코드 작성 (이론편)
    • Gson
    • 서비스 테스트 코드 작성
    베오
    베오

    티스토리툴바