요청받은 데이터를 검증하는 이유
- Client 개발자가 깜빡하거나 실수로 값을 안보낼 수도 있다.
- Client bug 로 값이 누락될 수 있다.
- 외부에 나쁜 사람이 값을 임의로 조작해서 보낼 수 있다.
- DB 에 값을 저장할 때 의도치 않은 오류가 발생할 수 있다.
위처럼 여러가지 이유로 서버 개발자의 불안한 마음을 편하게 하기 위해서 데이터 검증을 해야한다.
검증 없이는 아래와 같이 title 에 빈 문자열( "" ) 을 넣어도 테스트는 통과한다.
PostControllerTest
@DisplayName("/posts 요청 시 title 값은 필수다.")
@Test
void post() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\" : \"\", \"content\" : \"글 내용입니다 하하\"}")
)
.andExpect(status().isOk())
.andExpect(content().string("Hello"))
.andDo(print());
}
하지만 이는 제목에 빈값을 허용하지 않는 개발자의 의도와 다르기 때문에 우리는 데이터 검증을 해야한다.
검증은 어떻게 할까?
컨트롤러에 params 객체를 통해 전달된 title 값을 검증하는 로직을 작성해보자. 얼추 아래처럼 될 것이다.
위와 동일한 테스트를 시행해보자.
PostController
@PostMapping("/posts")
public String post(@RequestBody PostCreate params) throws Exception {
log.info("params = {}", params.toString());
String title = params.getTitle();
if (title == null || title.equals("")) {
throw new Exception("타이틀 값이 없어요!");
}
String content = params.getContent();
if (content == null || title.equals("")) {
// error
}
return "Hello";
}
예상대로 예외가 터진다. 하지만 이 방법은 많은 문제점을 가지고 있다.
- 우선 검증이 필요한 필드마다 노가다로 검증 코드를 작성해야해서 빡세다.
- 누락될 염려가 있다. (만약 필드가 100가지라면?)
- 생각보다 검증해야할 게 많다 (꼼꼼하지 않을 수 있다)
ex) 만약 입력된 title 값이 공백이 포함된 빈 문자열(" ") 이라면? 혹은 수십억글자라면?
특히 3번째가 가장 중요한데, 필드가 늘어감에 따라 코드 줄 수가 늘어나면 모든 예외 사항을 확인하기는 매우 힘들다.
무언가 3번 이상 반복 작업을 할 때 내가 뭔가 잘못하고 있는건 아닐지 의심해봐야한다.
BeanValidation
검증 로직은 우리만 고민하는게 아닌 많은 개발자들이 꾸준히 고민해오던 문제이다.
이 검증 로직을 모든 프로젝트에 적용할 수 있게 공통화하고 표준화 한 것이 바로 Bean Validation 이다.
BeanValidation 에 관련된 내용은 여기에 정리해두었다.
BeanValidation 을 적용하여 컨트롤러와 테스트를 작성해보자.
PostController
@Valid 와 BindingResult 를 적용하여 PostCreate 객체에 바인딩 시 오류가 발생해도 컨트롤러가 호출된다.
BindingResult 에 검증 오류값이 저장되면 해당 필드명과 에러 메시지를 map 에 넣어서 응답으로 반환한다.
@PostMapping("/posts")
public Map<String, String> post(
@RequestBody @Valid PostCreate params,
BindingResult result
) throws Exception {
if (result.hasErrors()) {
List<FieldError> fieldErrors = result.getFieldErrors();
FieldError fieldError = fieldErrors.get(0);
String fieldName = fieldError.getField();
String errorMessage = fieldError.getDefaultMessage();
Map<String, String> error = new HashMap<>();
error.put(fieldName, errorMessage);
return error;
}
return Map.of();
}
PostCreate
DTO 의 검증하고자 하는 필드에 BeanValidation 에서 제공하는 검증 애노테이션을 사용한다.
여기서는 빈값 / 공백(" ")만 있는 경우를 허용하지 않는 @NotBlank 를 사용하고 defaultMessage 를 설정했다.
@Setter
@Getter
@ToString
public class PostCreate {
@NotBlank(message = "제목을 입력해주세요.")
private String title;
@NotBlank(message = "내용을 입력해주세요.")
private String content;
public PostCreate(String title, String content) {
this.title = title;
this.content = content;
}
}
PostControllerTest
title 값에 null 을 입력하면 응답 값으로 필드명("title")과 에러 메시지가 넘어오는지 확인하는 테스트이다.
@DisplayName("/posts 요청 시 title 값은 필수다.")
@Test
void post() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(
"{\"title\" : null, \"content\" : \"글 내용입니다 하하\"}"
)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.title").value("제목을 입력해주세요."))
.andDo(print());
}
테스트를 돌려보면 성공한다.
MockMvc 의 jsonPath 를 이용하면 JSON 을 검증하는 더 풍부한 테스트를 작성할 수 있다.
jsonPath 로 검증하는법은 더 공부해봐야겠다.
'API 만들어 보기 > 게시판 API' 카테고리의 다른 글
[작성글 저장] 리팩토링 1 (0) | 2023.09.23 |
---|---|
[작성글 저장] 게시글 저장 구현 (1) | 2023.09.22 |
[데이터 검증] @ControllerAdvice (0) | 2023.09.22 |
[작성글 저장] 컨트롤러 Mock 테스트 2 (0) | 2023.09.21 |
[게시글 조회] 컨트롤러 Mock 테스트 1 (0) | 2023.09.21 |