Spring boot/기본 정리

Unit Test - Controller

코딩딩코 2022. 12. 20.

Web Layer 중 컨트롤러 영역에 대한 Unit Test를 해보겠습니다.

 

Given-When-Then 패턴으로 사용합니다.

 

Given

 테스트에서 구체화하고자 하는 행동을 시작하기 전에 테스트 상태를 설명하는 부분

When

 구체화하고자 하는 그 행동

Then

 어떤 특정한 행동 때문에 발생할 거라고 예상되는 변화에 대한 설명

 

https://dhmk47.tistory.com/89

 

Unit Test - Service

저번글에 이어서 이번에는 Service 계층의 단위 테스트를 진행하겠습니다. 진행 과정 @RequiredArgsConstructor @Service public class ProductService { private final ProductRepository productRepository; private final AlertService ale

dhmk47.tistory.com

https://dhmk47.tistory.com/90

 

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

댓글