Web Layer 중 컨트롤러 영역에 대한 Unit Test를 해보겠습니다.
Given-When-Then 패턴으로 사용합니다.
Given
테스트에서 구체화하고자 하는 행동을 시작하기 전에 테스트 상태를 설명하는 부분
When
구체화하고자 하는 그 행동
Then
어떤 특정한 행동 때문에 발생할 거라고 예상되는 변화에 대한 설명
Unit Test - Service
저번글에 이어서 이번에는 Service 계층의 단위 테스트를 진행하겠습니다. 진행 과정 @RequiredArgsConstructor @Service public class ProductService { private final ProductRepository productRepository; private final AlertService ale
dhmk47.tistory.com
Unit Test - Repository
https://dhmk47.tistory.com/88 Unit Test - Controller Web Layer 중 컨트롤러 영역에 대한 Unit Test를 해보겠습니다. Given-When-Then 패턴으로 사용합니다. Given 테스트에서 구체화하고자 하는 행동을 시작하기 전에
dhmk47.tistory.com
@ExtendWith(MockitoExtension.class)
class ProductRestControllerTest {
@InjectMocks
private ProductRestController productRestController;
@Mock
private ProductService productService;
private Gson gson;
private MockMvc mockMvc;
@BeforeEach
public void init() {
gson = new Gson();
mockMvc = MockMvcBuilders.standaloneSetup(productRestController)
.setControllerAdvice(new CustomExceptionHandler())
.build();
}
}
단위 테스트이기 때문에 @SpringBootTest를 사용하지 않고 @MockMvc를 커스텀해서 단위 테스트를 진행하겠습니다.
또 Controller에서 Json형태로 데이터를 받아 처리해야 하기 때문에 Gson을 사용하겠습니다.
@BeforeEach는 @Test, @RepeatedTest, @ParameterizedTest 등의 어노테이션이 붙은 테스트가 진행되기 전에
실행됩니다.
gson과 mockMvc를 생성해줍니다.
예외를 ExceptionHandler를 통해서 잡아주려고 하기 때문에 setControllerAdvice를 통해서 직접 주입해줍니다.
@WebMvcTest를 사용하면 직접 설정해주지 않아도 됩니다.
MockMvc.isNotNull()
@Test
public void mockMvc가Null이아님() throws Exception {
assertThat(mockMvc).isNotNull();
}
mockMvc가 Null이 아닌지 확인을 위해 만들었습니다.
테스트는 통과하였습니다.
Get - Code 400
@Test
public void 제품조회실패() throws Exception {
// given
String url = "/api/v1/product/냉장고";
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.get(url)
.characterEncoding("utf-8"));
// then
resultActions.andExpect(status().isBadRequest());
}
실패 코드를 먼저 작성했습니다.
보기 편하게 url에 한글을 넣었습니다.
문자가 깨지기 때문에 utf-8로 인코딩 설정을 해줍니다.
@GetMapping("/{productName}")
public ResponseEntity<?> getProduct(@PathVariable String productName) {
if(!productName.equals("세탁기")) {
return ResponseEntity.badRequest().body("failed");
}
return ResponseEntity.ok("success");
}
간단하게 넘어온 url의 마지막 값이 세탁기가 아니라면 400 에러를 주도록 했습니다.
테스트는 통과하였습니다.
Post - Code 400 (Exception Handler)
@Test
public void 제품등록실패() throws Exception {
// given
String url = "/api/v1/product";
CreateProductRequestDto createProductRequestDto = new CreateProductRequestDto("김치냉장고");
when(productService.createProduct(createProductRequestDto)).thenThrow(new ProductException(ProductErrorResult.ALREADY_HAS_PRODUCT_EXCEPTION));
// when
final ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.post(url)
.content(gson.toJson(createProductRequestDto))
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("utf-8"));
// then
resultActions.andDo(print());
resultActions.andExpect(status().isBadRequest());
}
Mockito를 이용해서 예외를 던진다고 임의로 설정하였습니다.
andDo(print())로 요청/응답 전체 메시지를 확인할 수 있습니다.
Json으로 넘겨주어야 하는 데이터가 있기 때문에 gson을 사용하고 contentType을 잡아줍니다. 기댓값은 400 에러입니다.
@RestControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler({ProductException.class})
public ResponseEntity<?> alreadyHasProductException(ProductException exception) {
return makeErrorResponseEntity(exception.getProductErrorResult());
}
private ResponseEntity<?> makeErrorResponseEntity(ProductErrorResult errorResult) {
return ResponseEntity.status(errorResult.getHttpStatus())
.body(new ErrorResponse(errorResult.name(), errorResult.getMessage()));
}
}
@RequiredArgsConstructor
@Getter
public class ProductException extends RuntimeException {
private final ProductErrorResult productErrorResult;
}
@Getter
@RequiredArgsConstructor
public enum ProductErrorResult {
ALREADY_HAS_PRODUCT_EXCEPTION(HttpStatus.BAD_REQUEST, "Already Has Product Exception");
private final HttpStatus httpStatus;
private final String message;
}
ExceptionHandler를 처리하기 위해 만들었습니다.
@PostMapping("")
public ResponseEntity<?> createProduct(@RequestBody CreateProductRequestDto createProductRequestDto) {
CreateProductResponseDto product = productService.createProduct(createProductRequestDto);
return ResponseEntity.ok(product);
}
body에 담긴 데이터도 잘 넘어왔고 400 에러가 떴습니다.
테스트는 통과했습니다.
Post - Code 200
@Test
public void 제품등록성공() throws Exception {
// given
String url = "/api/v1/product";
CreateProductRequestDto createProductRequestDto = new CreateProductRequestDto("김치냉장고");
CreateProductResponseDto createProductResponseDto = new CreateProductResponseDto(1, "김치냉장고");
when(productService.createProduct(createProductRequestDto)).thenReturn(createProductResponseDto);
// when
final ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.post(url)
.content(gson.toJson(createProductRequestDto))
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("utf-8"));
// then
resultActions.andExpect(status().isOk());
}
이번엔 ResponseDto를 반환하게끔 stub 했습니다.
테스트는 통과했습니다.
Get - Code 200
@Test
public void 제품조회성공() throws Exception {
// given
String url = "/api/v1/product/세탁기";
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.get(url)
.characterEncoding("utf-8"));
// then
resultActions.andExpect(status().isOk());
}
@GetMapping("/{productName}")
public ResponseEntity<?> getProduct(@PathVariable String productName) {
if(!productName.equals("세탁기")) {
return ResponseEntity.badRequest().body("failed");
}
return ResponseEntity.ok("success");
}
테스트는 통과했습니다.
모든 테스트 실행
이렇게 전체 Controller 단위 테스트는 통과하였습니다.
간단하게 만든 것이기 때문에 복잡한 로직으로 구현하진 않았습니다.
'Spring boot > 기본 정리' 카테고리의 다른 글
Unit Test - Repository (0) | 2022.12.21 |
---|---|
Unit Test - Service (0) | 2022.12.21 |
@MockMvc, @SpringBootTest, @WebMvcTest (0) | 2022.12.20 |
Mokito @Mock , @MockBean , @Spy , @SpyBean (0) | 2022.12.19 |
TDD(Test Driven Development) (0) | 2022.12.18 |
댓글