Spirng Validation 유효성체크
스프링에서 API를 만들때 어떤형태로 요청이 들어올지 우리는 모른다. 그렇게 때문에 들어오는 요청의 에대해서 유효성체크(validation)을 할필요성이 있다.
선언적인 방식의 유효성 검사기
- Spring validator
-
Hibernate validator
- @Valid :
JSR-303
에 의해 만들어진 어노테이션 (현재는JSR-380
까지 나온상태) - @Validated : spring framework 의 만들어진 어노테이션
Package Tree
패키지 구조는 아래와 같다
자료는 저장소 바로가기 에서 다운받을수 있다.
STAR도 한번씩 눌러주시길 (굽신..)
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── demo
│ │ ├── Controller.java
│ │ ├── DemoApplication.java
│ │ ├── domain
│ │ │ └── Person.java
│ │ ├── enums
│ │ │ └── CategoryType.java
│ │ ├── params
│ │ │ └── PersonParam.java
│ │ ├── request
│ │ │ └── PersonRequest.java
│ │ ├── service
│ │ │ └── PersonService.java
│ │ └── validator
│ │ └── PersonValidator.java
│ └── resources
│ ├── application.properties
│ ├── static
│ └── templates
└── test
└── java
└── com
└── example
└── demo
├── DemoApplicationTests.java
├── PersonApiTest.java
└── PersonTest.java
Controller
/src/main/java/com/example/demo/Controller.java
를 살펴보면 POST method로 지정되어있으며 json
형태로 입력을 받고 있다. 또한 @Valid
를 이용하여 요청이 들어오는 입력값에 대해서 검증을 실시하고 BindingResult
필드를 통하여 error
에대한 결과가 컨트롤러로 넘겨받는다. 필드를 이용하여 유효성실패에대한 적절한 분기처리
를 할수있다.
@RestController
public class Controller {
@RequestMapping(value = "/", method = RequestMethod.POST)
@ResponseStatus(code = HttpStatus.CREATED)
public PersonRequest getPerson(@RequestBody @Valid PersonRequest personRequest, BindingResult bindingResult){
new PersonValidator().validate(personRequest, bindingResult);
if(bindingResult.hasErrors()){
bindingResult.getAllErrors()
.stream()
.forEach(s -> {
log.error("[{}] {}", s.getCode(), s.getDefaultMessage());
});
return null;
}
return personRequest;
}
}
PersonRequest
Person.class
의 형태는 아래처럼 되어있으며 annotation으로 값이 없을때 그리고 최소값을 가져야하는 유효성 및 메세지를 지정하였다.
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Person {
@NotEmpty(message = "이름을 입력해주세요.")
private String name;
@Min(value = 1, message = "나이는 0 이상입니다. ")
private int age;
@Builder
public Person(@NotEmpty(message = "이름을 입력해주세요.") String name, @Min(value = 1, message = "나이는 0 이상입니다. ") int age) {
this.name = name;
this.age = age;
}
}
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class PersonParam {
private Long testLong;
@NotNull(message = "정확한 카테고리값을 입력해주세요.")
private CategoryType categoryType;
@Builder
public PersonParam(Long testLong, @NotNull(message = "정확한 카테고리값을 입력해주세요.") CategoryType categoryType) {
this.testLong = testLong;
this.categoryType = categoryType;
}
}
Request 최종
위의 객체들을 합쳐서 PersonRequest
에 몰아 넣어 각각 필드에 @Valid
를 적용하여 각 객체속의 검증도 하도록 적용하였다.
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class PersonRequest {
@Valid
private PersonParam personParam;
@Valid
private Person person;
@Builder
public PersonRequest(@Valid PersonParam personParam, @Valid Person person) {
this.personParam = personParam;
this.person = person;
}
}
Request Json Body
일단 정상적인 케이스를 시도해보겠다.
{
"personParam":{
"categoryType": "CAR"
},
"person": {
"name": "dasda",
"age": 2
}
}
실행결과
여기서 주목해봐야할 값은 Respon Status 201
로 성공했다는거 그리고 테스를 보다 쉽게 하기 위해서 들어온값 그대로 응답하였다.
Validator Class
좀더 세밀하게 유효성검사를 하고 싶을땐 따로 클래스를 만들어 Validator
를 상속받아 구현해줄수 있다.
파일위치 : /src/main/java/com/example/demo/validator/PersonValidator.java
public class PersonValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return PersonRequest.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
PersonRequest personRequest = (PersonRequest) target;
// TODO: 유효성검사
//errors.reject("arthur", "이곳에걸렷구나");
}
}
boolean supports(Class<?> clazz)
는 유효성을 체크할려는 클래스에 대한 허용여부 True로 될시 아래의 void validate(Object target, Errors errors)
로 진입하게 된다.
또한 유효성 체크시 적절하게 errors.reject()
를 이용하여 컨트롤러에게 에러사실을 전달할수있다.
Fail Request Json
일부러 categoryType
을 enum 범위에 없는 값을 입력하여 유효성 체크를 해보자
{
"personParam":{
"categoryType": "BIKE"
},
"person": {
"name": "dasda",
"age": 2
}
}
컨트롤러의 유효성 bindingResult.hasErrors()
에 걸려 에러로그를 콘솔에 출력하고 있다.
나의 Validator Class 컨트롤러에 적용하기
내가만든 유효성클래스를 컨트롤러에 적용하기 위해선 new PersonValidator().validate(personRequest, bindingResult);
호출해주면된다.
API Test Case
자 이제 최종적으로 테스트케이스를 작성해보자.
MockMvc
테스트를 하기위해서 의존성 주입을 한후 request객체를 만들어 build해준다.
@RunWith(JUnitPlatform.class)
@WebMvcTest(Controller.class)
public class PersonApiTest {
@Autowired
private MockMvc mvc;
@Test
@DisplayName("API MOCK Test")
public void personAPI테스트() throws Exception {
Person person = Person.builder()
.age(32)
.name("arthur")
.build();
PersonParam personParam = PersonParam.builder()
.categoryType(CategoryType.CAR)
.testLong(1L)
.build();
PersonRequest personRequest = PersonRequest.builder()
.person(person)
.personParam(personParam)
.build();
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = ow.writeValueAsString(personRequest);
mvc.perform(
post("/")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(json)
).andExpect(status().isCreated())
.andDo(print());
}
}
정상적으로 pass
된것을 확인했다. 응답결과값도 우리가 원하는 대로 넘어온걸 확인했다.
앞으로 controller, service등 validation을 적절하게 이용하여 잘 쓸수 있을거 같다.
또한 추가로 요청할때 enum에 없는 값들이 넘어왔을때 처리방법을 알아보겠다.