컨트롤러는 클라이언트로 부터 요청을 받아 요청에 걸맞은 서비스 컴포넌트로 요청을 전달
하고 그 결과값을 가공해서 클라이언트에게 응답
을 하는 역할
컨트롤러의 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 객체를 생성해서 주입하는 역할 수행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>