예외처리

기존의 단건 조회처럼 코드 작성 시 에러 메시지가 바뀌면 관련 코드나 테스트코드 다 바꿔야한다.

또한 IllegalArgumentException 은 자바에서 제공해주는 예외이기 때문에 비즈니스를 명확하게 표현하기 힘들다.

 

기존 코드

public PostResponse get(Long id) {
    Post post = postRepository.findById(id).orElseThrow(
        () -> new IllegalArgumentException("존재하지 않는 글입니다.")
    );
    return new PostResponse(post);
}

따라서 이름부터 전달하는 메시지가 명확한 PostNotFoundException 이라는 커스텀 예외를 만들어 적용한다.

아래 사진처럼 동작하는 커스텀 예외를 만들어보자.

 

 


PostService

컨트롤러는 기존과 동일하고 조회 메서드인 get() 에 PostNotFoundException 을 적용한다.

public PostResponse get(Long id) {
    Post post = postRepository.findById(id).orElseThrow(
        () -> new PostNotFoundException()
    );
    return new PostResponse(post);
}

 

PostNotFoundException

언체크드 익셉션인 RuntimeException 을 상속받는다.

예외는 예외 메시지(message)발생한 원인(cause) 를 생성자의 매개변수로 받을 수 있다.

해당 커스텀 예외는 이름부터 게시글을 찾을 수 없다는 메시지가 명확하므로 예외 메시지는 내부에서 처리한다.

이후 추가 생성자가 필요할 시 추가하고 지금은 간단하게 기본 생성자만 구현한다.

public class PostNotFoundException extends RuntimeException{

    private static final String MESSAGE = "존재하지 않는 글입니다.";

    public PostNotFoundException() {
        super(MESSAGE);
    }
}

 

PostServiceTest

존재하지 않는 게시글을 단건 조회 시 PostNotFoundException 이 발생할 것을 기대하는 테스트를 작성한다.

@DisplayName("글 단건 조회 실패 - 존재하지 않는 글")
@Test
void get1() {
    // given
    Post post = Post.builder()
        .title("우리 이쁜 미카공주님")
        .content("우리 공주님")
        .build();
    postRepository.save(post);

    // expected
    assertThrows(
        PostNotFoundException.class,
        () -> postService.get(post.getId() + 1000)
    );
}

 


PostControllerTest

존재하지 않는 글을 조회하는 테스트이므로  상태코드는 404 NotFound 로 응답 받길 기대하는 테스트를 작성한다.

@DisplayName("글 단건 조회 실패 - 존재하지 않는 글")
@Test
void get1() throws Exception {
    // expected
    mockMvc.perform(
            MockMvcRequestBuilders.get("/posts/{postId}",1000L)
                .contentType(APPLICATION_JSON)
        )
        .andExpect(status().isNotFound())
        .andDo(print());
}

하지만 아직 컨트롤러 어드바이스에서 우리가 만든 커스텀 예외의 예외처리를 하지 않았기 때문에

컨트롤러 테스트 시 아래처럼 500 에러가 뜬다.

 

 

PostControllerAdvice

이제 아래처럼 컨트롤러 어드바이스에서 예외처리를 하고 다시 테스트를 돌려보자.

@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(PostNotFoundException.class)
public ErrorResponse postNotFoundExceptionHandler(PostNotFoundException e) {
    return ErrorResponse.builder()
        .code("404")
        .message(e.getMessage())
        .build();
}

 

테스트 수행 시 우리가 의도한 대로 동작하며 테스트가 성공함을 알 수 있다.

이제 다른 동일한 예외가 발생하는 수정, 삭제 메서드의 코드들도 수정하자. 그 과정을 적지는 않겠다.

 


추가로

만약 위 컨트롤러 테스트에서 빈 validation 객체가 응답으로 내려오는게 싫다면

ErrorReponse 를 아래처럼 리팩토링한다.

 

ErrorResponse

@Getter
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
public class ErrorResponse {

    private final String code;
    private final String message;
    private final Map<String, String> validation = new HashMap<>();

    @Builder
    public ErrorResponse(String code, String message) {
        this.code = code;
        this.message = message;
    }

    public void addValidation(String fieldName, String errorMessage) {
        this.validation.put(fieldName, errorMessage);
    }
}

ㄴ@JsonInclude(value = JsonInclude.Include.NON_EMPTY)

해당 애노테이션을 사용하면 빈 값은 JSON 응답으로 내려주지 않는다.

 

 

테스트 수행 시 우리가 의도한 대로 빈 validation 값은 JSON 응답으로 보내지 않았다.

하지만 빈 값도 일종의 정보이기 때문에 사용 시 고려해서 써야할것같다.

+ Recent posts