적합한 테스트 가이드
레이어(layer)별로 사용하기 적합한 방식의 테스트 가이드에 대해 알아보겠습니다.
그 중에 '컨트롤러 객체'를 테스트하는 방법을 알아보겠습니다.
컨트롤러 객체의 테스트
- 컨트롤러는 클라이언트로부터 요청을 받아 요청에 걸맞는 서비스 컴포넌트로 요청을 전달한다.
- 그 결과값을 가공해서 클라이언트에게 응답하는 역할을 수행한다.
- 즉, 애플리케이션을 구성하는 여러 레이어 중에 가장 웹에 가까이에 있는 모듈이라고 볼 수 있다.
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) 테스트라고 부른다.
- 슬라이스 테스트
- 단위 테스트와 통합 테스트의 중간 개념으로 이해하면된다.
- 레이어드 아키텍처를 기준으로 각 레이어별로 나누어 테스트를 진행한다는 의미이다.
- 모든 외부 요인을 차단하고 테스트를 진행해야하지만, 컨트롤러는 개념상 웹과 맞닿은 레이어로써 외부 요인을 차단하고 테스트하면 의미가 없기 때문에 슬라이스 테스트를 진행하는 경우가 많다.
2️⃣ @MockBean
- 실제 빈 객체가 아닌 Mock(가짜) 객체를 생성해서 주입하는 역할을 수행한다.
- ex) ProductController가 의존성을 가지던 ProductService의 객체에 Mock 객체를 주입했다.
- @MockBean이 선언된 객체는 실제 객체가 아니기 때문에 실제 행위를 수행하지 않는다.
- 해당 객체는 개발자의 Mockito의 given() 메서드를 통해 동작을 정의해야한다.
- given() : 어떤 객체에서 어떤 메서드가 호출되고 어떤 파라미터를 주입받는 지 가정한다.
- willReturn() : 어떤 결과를 리턴할 것인지 정의하는 구조로 작성한다.
- 메서드의 이름에서 알 수 있듯이 이부분의 코드가 앞에 설명한 Given에 해당한다.
3️⃣ @Test
- 테스트 코드가 포함되어 있다고 선언하는 어노테이션이다.
- JUnit Jupiter에서는 이 어노테이션을 감지하여 테스트 계획에 포함시킨다.
4️⃣ @DisplayName
- 테스트 메서드의 이름이 복잡해서 가독성이 떨어지는 경우, 이 어노테이션을 통해 테스트에 대한 표현을 정의할 수 있다.
5️⃣ 그 외의 구성
- MockMvc
- 컨트롤러의 API를 테스트하기 위해 사용된 객체이다.
- Mock(가짜) 객체를 사용해 Spring MVC 동작을 재현할 수 있는 테스트 프레임워크
- perform() 메서드
- 서버로 URL 요청을 보내는 것처럼 통신 테스트 코드를 작성해서 컨트롤러를 테스트할 수 있다.
- MockMvcRequestBuilders에서 제공하는 HTTP 메서드로 URL을 정의해서 사용한다.
- MockMvcRequestBuilders는 GET, POST, PUT, DELETE에 매핑되는 메서드를 제공한다.
- MockMvcServletRequestBuilder 객체를 리턴하며, HTTP 요청 정보를 설정할 수 있게 된다.
- 결과값으로 ResultActions 객체가 리턴되는데, andExpect() 메서드를 활용하여 결과값을 검증할 수 있다.
- andExpect() 메서드
- ResultMatcher를 활용한다.
- MockMvcResultMatchers 클래스에 정의되어 있는 메서드를 활용해 생성할 수 있다.
- andDo() 메서드
- MockMvc 코드는 모두 합쳐져 있어 구분하기는 애매하다.
- 하지만, 전체적인 'When-Then' 구조를 갖추고 있음을 확인할 수 있다.
- verify() 메서드
- 지정된 메서드가 실행되었는지 검증하는 역할이다.
- 일반적으로 given()에 정의된 동작과 대응한다.
반응형
'TEST > JUnit' 카테고리의 다른 글
단위 테스트 방법 [part 3 : 리포지토리(Repository)] (0) | 2023.11.08 |
---|---|
단위 테스트 방법 [part 2 : 서비스(Service)] (0) | 2023.11.08 |
JUnit 테스트 코드의 설명과 예제 (0) | 2023.11.07 |
댓글