• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

@ControllerAdvice 없이 예외처리 하는 방법은 어떻게 되나요?

22.11.25 11:47 작성 조회수 663

2

안녕하세요 호돌맨님 강의 잘 듣고 있습니다!

강의를 들으면서 저도 비슷한 방법으로 게시판을 만들어 보려고 하는데요. 예외처리 부분에서 문득 궁금증이 생겨서 질문을 드립니다.

혹시 @ControllerAdvice 없이 예외처리하는 방법은 뭐가 있을까요?

Controller

@GetMapping("/{postId}")
public ResponseEntity<ResponsePostDto> getPost(@PathVariable Long postId) {
    ResponsePostDto responsePostDto = postService.get(postId);

    return ResponseEntity.ok(responsePostDto);
}

Service

public ResponsePostDto get(Long postId) {
    Post post = postRepository.findById(postId)
            .orElseThrow(() -> new RuntimeException("존재하지 않는 게시글입니다."));

    return ResponsePostDto.builder()
            .id(post.getPostId())
            .title(post.getTitle())
            .content(post.getContent())
            .createdDate(post.getCreatedDate())
            .build();
}

컨트롤러와 서비스를 이렇게 작성하고 @ControllerAdvice 없이 테스트를 돌려보니 서비스 계층에서의 예외처리 테스트 코드는 RumtimeException을 받으면서 잘 돌아갑니다.

그리고 컨트롤러 테스트 코드를 작성했습니다.

ControllerTest

@Test
@DisplayName("게시글 조회 실패 - 잘못된 ID")
void getPostBadIdTest() throws Exception {
    // given
    ResponseSavedIdDto responseSavedIdDto = postService.write(RequestRegisterPostDto.builder()
            .title("test title")
            .content("test content")
            .build());

    Long postId = responseSavedIdDto.getSavedId();

    // expected
    mockMvc.perform(get("/posts/{postId}", postId + 1L)
                    .contentType(APPLICATION_JSON))
            .andExpect((result) -> {
                System.out.println("===================");
                System.out.println("message: " + result.getResolvedException().getMessage());
                System.out.println("===================");
                Assertions.assertEquals(result.getResolvedException().getClass().getCanonicalName(), RuntimeException.class.getCanonicalName());
                Assertions.assertTrue(result.getResolvedException().getClass().isAssignableFrom(RuntimeException.class));
            })
            .andDo(print());
}

이렇게 컨트롤러 테스트를 돌려보니 서비스에서 터진 RuntimeException이 컨트롤러까지 전달이 안되는것으로 확인했습니다.

그리고 나서 컨트롤러에서 try catch로 RuntimeException을 잡는 방식으로 수정해봤습니다.

@GetMapping("/{postId}")
public ResponseEntity<ResponsePostDto> getPost(@PathVariable Long postId) {
    try {
        ResponsePostDto responsePostDto = postService.get(postId);

        return ResponseEntity.ok(responsePostDto);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

이렇게 하니 RumtimeException은 컨트롤러에서 터지긴 하는데 테스트 코드에서는 andExpect에서 Exception 결과를 받지 못하는지 통과가 계속 안됩니다..ㅠㅠ

강의에서 나온대로 @ControllerAdvice와 @ExceptionHandler를 사용하면 통과가 되는데 컨트롤러에서 try catch로 Exception을 던지는것과 어떤 차이가 있길래 테스트 코드에서 차이점이 생기는 걸까요? 그리고 @ControllerAdvice를 사용하지 않는다면, 서비스 계층에서 생긴 Exception을 어떻게 컨트롤러에서 처리하면서 테스트 코드를 통과할 수 있을까요??

답변 주시면 감사하겠습니다!!

답변 1

답변을 작성해보세요.

4

안녕하세요. 호돌맨입니다.
답변이 늦어서 죄송합니다.

사실 저는 님께서 테스트 해본 방식대로 controller에서 예외를 터쳐볼 생각을 못해봤네요. 대단하신것 같습니다.

이미 해결을 하셨을것 같지만 그래도 답변 남겨봅니다.

이런 문제가 발생하는 이유는, 테스트는 http로 소통하기를 원하지만 예외는 programmatically하게 발생하여 요청이 죽어버렸기 때문입니다.
@ControllerAdvice, @ExceptionHandler를 사용하여 http통신 자체는 죽이지 않고 프로세스 중간에 발생한 예외를 getResolvedException 를 통해 알 수 있는것 같습니다.

예를들어

  • 제가 택배(http)를 보낸다고 합시다. 수령인이 부재중이라 택배가 반송(exception)되면 반송 원인(getResolvedException)을 택배사 홈페이지를 통해 알 수 있습니다. 그렇게 알려주는 게 택배회사의 프로세스이고 시스템이니깐요.

  • 그런데 배송 중간에 택배기사가 추노를 때리면 제가 알길이 없습니다.

  • 그렇기 때문에 배송과정에 감시장치(ControllerAdvice)를 달아두는 게 의미 있겠져.

ControllerAdvice 를 없애고 컨트롤러에서 발생하는 예외만 테스트 하고싶으시다면 mockMvc를 통하지 마시고 (new PostController).getPost() 형태로 직접 인스턴트화 해서 테스트 해보시면 어떨까 싶습니다.

다시 한 번 답변 늦어서 죄송하고
감사합니다.

kimwoody님의 프로필

kimwoody

질문자

2022.12.16

호돌맨님 답변 정말 감사드립니다!

답변이 늦어져서 혼자 고민해보는 시간을 가졌는데, 제가 고민해서 내린 결론과 같은 말씀을 하시는 것 같습니다!

저도 어차피 @ControllerAdvice를 사용할 예정이라서 가볍게 넘길수도 있었는데 안되는 이유가 너무 궁금해서 질문까지 남기게 됐습니다. 다시 한 번 답변 감사합니다. 말씀해주신것처럼 mockMvc 말고 직접 인스턴스 선언해서 테스트해보니 테스트도 잘 돌아갑니다!

강의와 답변 모두👍