본문 바로가기

Backend/SpringBoot

SpringBoot API 요청 값 검증하고 Validation Exception Handing하기

안녕하세요!

이번 포스팅에서는 SpringBoot의 Validation을 이용해서

예외를 발생시키고 처리하는 방법에 대해 알아보겠습니다. 🤗

 

예전에 Spring Boot Validation을 사용하여 요청 값 검증하기라는 주제로 Validation을 이용해서 요청값을 검증하고 에러를 뷰로 리턴하는 방법에 대해 포스팅했는데,

이번에는 뷰를 리턴해서 템플릿 엔진으로 처리하는게 아닌,

API 요청이 왔을 때 요청값을 검증하고 에러가 발생했을 때 응답을 보내는 방법에 대해 알아보도록 하겠습니다.

 

전체 코드는 Github에서 확인이 가능합니다.


사전 준비

1. 프로젝트 설명

도메인 객체에서 어노테이션을 이용해서 Validaion 처리를 하고,

Validaion에서 걸렸을때, @RestControllerAdvice를 이용해서 예외를 처리하는 코드를 작성해보도록 하겠습니다.

2. 의존성

(SpringBoot에서 프로젝트를 만들 때 기본적으로 추가할 수 있는 의존성을 의미합니다)

  • Web
  • Lombok
  • Test

 


구현

1. 도메인 객체 작성

Obj.java

@AllArgsConstructor
@Getter
public class Obj {

  @NotBlank(message = "이름")
  private String name;

  @NotNull(message = "나이")
  private Integer age;

}

가장 먼저 검증을 할 도메인 객체를 작성해줍니다.

어노테이션의 message 속성값으로는 검증 실패 시 반환할 메세지를 작성합니다.

검증에 실패한 메세지는 템플릿을 맞춰두었기 때문에 message 속상 값으로 실패한 대상만 적어두었습니다.

어노테이션들의 의미(@NotBlank, @NotNull)는 이전 포스팅에서 정리했기 때문에 따로 정리하지 않겠습니다!

 

2. 응답 객체 작성

@Getter
public class RestReponse {

    private boolean success;
    private String message;
    private Object data;

    public RestReponse(boolean success, String message) {
        this.success = success;
        this.message = message;
    }
}

응답 객체를 만들어줍니다.

응답 객체는 성공 여부(success), 메세지(message), 응답 데이터(data)로 구성되어있습니다.

성공 여부와 메세지를 초기화해주는 생성자도 작성해줍니다.

일반적으로는 성공했을 때를 위해 data를 받는 생성자(또는 정적 메소드)도 만드는 게 일반적이지만, 따로 만들지 않았습니다.

 

3. 컨트롤러 작성

ValidController.java

@RestController
public class ValidController {

  @PostMapping("/valid")
  public Obj valid(@RequestBody @Valid Obj object){
    return object;
  }
}

POST 요청으로 값을 받고, 받는 개체 앞에 @Valid 어노테이션을 추가해주어 검증 요청을 합니다.

여기서 name의 값이 비어있거나, age가 null이면 MethodArgumentNotValidException이 발생합니다.

이 예외를 처리하기 위해 예외처리 핸들러를 작성해줍니다.

 

4. 예외 처리 핸들러 작성

만약 예외처리 핸들러를 한 번도 사용해본 적이 없으면,

제가 예전에 작성했던 ControllerAdvice를 이용하여 JSON 형태로 예외처리하는 방법을 읽으시면 도움이 될 것 같습니다!

AjaxBadResponseHandler.java

@RestControllerAdvice
public class AjaxBadResponseHandler {

  @ExceptionHandler({MethodArgumentNotValidException.class})
  public ResponseEntity<RestReponse> validException(
      MethodArgumentNotValidException ex) {

	  RestReponse restReponse = new RestReponse(false, // 1
        "유효성 검사 실패 : " + ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());

    return new ResponseEntity<>(restReponse, HttpStatus.BAD_REQUEST); // 2
  }
}

MethodArgumentNotValidException에 대한 예외 처리를 해주는 메소드를 작성해줍니다.

 

1.

아까 만들어둔 응답 객체(RestResponse) 생성자를 사용해서 성공 여부(false)와 메세지를 넣어서 객체를 만들어줍니다.

MethodArgumentNotValidException에서 BindingResult를 사용할 수 있는데,

BindingResult에는 Validaiton에서 걸린 변수의 message를 확인할 수 있습니다.

(.getBindingResult().getAllErrors().get(0).getDefaultMessage())

 

ResponseEntity를 이용해서 응답 결과 상태 값을 400으로 설정해주고,

success값과 message값이 담긴 RestReponse을 함께 응답합니다.


테스트

잘 동작하는지 확인하기 위해 테스트 코드를 작성해줍니다.

ValidControllerTest.java

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

@RunWith(SpringRunner.class)
@WebMvcTest(ValidController.class)
public class ValidControllerTest {

  @Autowired
  private MockMvc mockMvc;

  @Autowired
  private ObjectMapper objectMapper;


  @Test
  public void validation_실패() throws Exception {
    String content = objectMapper.writeValueAsString(new Obj("해어린", null));

    mockMvc.perform(post("/valid")
        .content(content)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON))
        .andExpect(status().is4xxClientError())
        .andExpect(jsonPath("$.message").value("유효성 검사 실패 : 나이"))
        .andDo(print());
  }

  @Test
  public void validation_성공() throws Exception {
    String content = objectMapper.writeValueAsString(new Obj("해어린", 10));

    mockMvc.perform(post("/valid")
        .content(content)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andDo(print());
  }

}

위 테스트코드 같이 나이에 null을 넣어 실패하는 테스트와,

정상적인 값을 넣어 성공하는 코드 작성했습니다.

 

첫 번째 코드는 나이에 null이 들어갔기 때문에 MethodArgumentNotValidException이 발생하고,

이를 예외 처리 핸들러에서(AjaxBadResponseHandler) 400 에러와 해당하는 메세지를 보내줍니다.

.andExpect() 메소드를 사용해서 4xx 에러의 여부와,

에러 메세지가 예상한 대로 응답을 했는지 확인해주는 코드를 작성했습니다.

 

두 번째 코드는 둘 다 예외가 발생하지 않는 값을 넘겨주고,

.andExpect() 메소드를 사용해서 상태 코드가 200인지 확인해주는 코드를 작성했습니다.

 

성공했습니다!

 

혹시 글을 읽으면서 잘못된 내용이 있으면 댓글로 알려주시면 감사하겠습니다!

읽어주셔서 감사합니다! 😊