spring으로 웹 or API 서버를 구축하다보면 필수 파라미터를 체크해야 하는 부분이 있다.
이전까지만 해도 Controller 나 Service에 파라미터를 직접 체크하는 로직을 구현했었는데, Spring에서 제공하는 Validation을 이용하면 쉽게 유효검증이 가능하다는 것을 알았다.
이참에 관련 내용을 정리하고자 한다.
여기서는 gradle, springboot를 이용했으며 테스트는 junit5를 이용했다.
gradle 설정
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-web'
// validation 추가
implementation 'org.springframework.boot:spring-boot-starter-validation'
// test(junit5)
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
Request를 오브젝트 생성
변수 위에 @NotBlank를 붙였는데, 저렇게 붙인것에만 필수값을 체크한다.
그밖에 @Size, @Min, @Max등 수치나 갯수를 체크하는 것도 있으니 용도에 맞게 가져다 쓰자.
@ToString
@Getter
public class PostTestRequest {
@NotBlank(message = "이름은 필수값 입니다.")
private String name;
@Min(value = 10, message = "10살 이후부터 가입가능합니다.")
private int age;
private String address;
}
컨트롤러 생성
Post이기 때문에 @RequestBody 를 붙였으며, 유효성검증을 위해 @Valid를 붙였다.
만약 @Valid를 붙이지 않는다면 유효성 검사를 하지 않는다.
@RestController
public class TestController {
@PostMapping("/test/post")
public String testPost(@RequestBody @Valid PostTestRequest request) {
System.out.println("request = " + request);
return "testPost";
}
}
테스트코드 작성
일부로 이름은 빈값을, 그리고 나이를 10살 미만인 5로 설정해서 전송한다.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TestControllerTest {
@Autowired
TestRestTemplate testRestTemplate;
@Test
@DisplayName("post parameter에 필수값 체크")
void testPostTest() {
// given
String url = "/test/post";
HashMap params = new HashMap();
params.put("age", 5);
params.put("address", "서울");
// when
ResponseEntity<String> entity = testRestTemplate.postForEntity(url, params, String.class);
// then
System.out.println("entity = " + entity);
}
}
결과확인
entity = <400,{"timestamp":"2020-06-22T23:28:28.944+0000","status":400,"error":"Bad Request","errors":[{"codes":["NotBlank.postTestRequest.name","NotBlank.name","NotBlank.java.lang.String","NotBlank"],"arguments":[{"codes":["postTestRequest.name","name"],"arguments":null,"defaultMessage":"name","code":"name"}],"defaultMessage":"이름은 필수값 입니다.","objectName":"postTestRequest","field":"name","rejectedValue":null,"bindingFailure":false,"code":"NotBlank"},{"codes":["Min.postTestRequest.age","Min.age","Min.int","Min"],"arguments":[{"codes":["postTestRequest.age","age"],"arguments":null,"defaultMessage":"age","code":"age"},10],"defaultMessage":"10살 이후부터 가입가능합니다.","objectName":"postTestRequest","field":"age","rejectedValue":5,"bindingFailure":false,"code":"Min"}],"message":"Validation failed for object='postTestRequest'. Error count: 2","path":"/test/post"},[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Mon, 22 Jun 2020 23:28:28 GMT", Connection:"close"]> |
로그를 보면 이름과 나이 둘다 문제가 있다고 보이는데, 이처럼 잘못된 파라미터에 한해서 List에 담아서 보내준다.
여기에 ExceptionHandler를 추가등록해서 result포멧을 통일시켜주면 좋을거 같다.
@ControllerAdvice(basePackages = "kr.sample.demo.controller")
public class ControllerAdviceHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public Map<String, Object> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, Object> result = new HashMap<>();
result.put("code", "ERROR");
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
result.put("errors", errors);
return result;
}
}
테스트를 실행하면 다음과 같은 결과를 확인할 수 있다.
entity = <400,{"code":"ERROR","errors":{"name":"이름은 필수값 입니다.","age":"10살 이후부터 가입가능합니다."}},[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Mon, 22 Jun 2020 23:42:49 GMT", Connection:"close"]> |
이번엔 mock을 이용해 테스트
(junit5 는 @Runwith대신 @ExtendWith를 사용한다.)
@ExtendWith(SpringExtension.class)
@WebMvcTest
@AutoConfigureMockMvc
class TestControllerTestV2 {
@MockBean
private TestController testController;
@Autowired
private MockMvc mockMvc;
@Test
@DisplayName("post 필수값 체크 테스트")
void testPostTest2() throws Exception {
// given
String url = "/test/post";
String test = "{\"age\":5, \"address\": \"서울\"}";
// when
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post(url)
.content(test)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isBadRequest())
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
// 기본 캐릭터셋이 ISO-8859-1 임을 확인
System.out.println("mvcResult = " + mvcResult.getResponse().getCharacterEncoding());
// 캐릭터셋 설정
String content = mvcResult.getResponse().getContentAsString(Charset.forName("UTF8"));
System.out.println("content = " + content);
// 테스트 결과 비교
Assertions.assertThat(content).contains("이름은 필수값 입니다");
}
}
테스트 결과
끝.
참고:
https://www.baeldung.com/spring-boot-bean-validation
https://lemontia.tistory.com/934
'공부 > 프로그래밍' 카테고리의 다른 글
[aws] Jenkins + S3 + CodeDeploy 를 이용해 배포하기(수동배포) (0) | 2020.07.16 |
---|---|
[data] airflow 설치(DB: mysql) (0) | 2020.07.02 |
[aws] EC2 에 JAVA 버전 11로 업데이트 하기 (0) | 2020.06.18 |
[springboot, aop] 반복적인 작업은 이제 그만, AOP로 해결하기 (0) | 2020.06.17 |
[frontend] SSR, 서버사이드 랜더링(next.js, getInitialProps) (2) | 2020.05.06 |
댓글