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 |