Validation
https://spring.io/guides/gs/validating-form-input
Getting Started | Validating Form Input
The application involves validating a user’s name and age, so you first need to create a class that backs the form used to create a person. The following listing (from src/main/java/com/example/validatingforminput/PersonForm.java) shows how to do so: pac
spring.io
- 특정 메서드나 파타미터에 사용되어 해당 객체가 유효성 검샅를 통과해야함을 나타낸다
- 주로 Spring mvc에서 사용되며 HTTP 요청의 데이터를 바인딩한 후 유효성 검사를 수행해야 한다
- 이를 통해 데이터의 일관성과 유효성을 보장하고 안정적인 애플리케이션을 구축 할 수 있다
검증 흐름
- 클라이언트는 데이터를 담아서 @RequestBody, @RequestParam, @PathVariable Annotation을 이용하여 API로 호출한다
- API에서는 @Valid 또는 @Vadlidated 어노테이션으로 데이터 유효성을 검증한다
- 이후 유효성 검증을 통과한 경우는 클라이언트로 ‘성공 응답’데이터를 전송하고 실패한 경우에는 MethodArgumentNotValidException에러가 발생하는데 이것을 @ControllerAdvice, @ExceptionHandler로 구성한 ‘GlobalException’에서 에러를 캐치한다
- 이후 에러는 클라이언트에게 ‘에러 응답’ 데이터로 전송된다
유효성 검사 어노테이션 종류
- 참고: https://dev-coco.tistory.com/123
적용
코드작성
- 의존성추가
- spring boot 2.3 version 이전에는 spring-boot-starter-web 의존성 내부에 validation이 있었지만, spring boot 2.3 version 이상부터는 모듈로 빠져 별도의 의존성을 추가해줘야 한다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
- QuestionsOption
- tags: List<String>여서 별도의 Valid를 생성해주었다 - customValidation문서 참고
- count: @Positive 어노테이션을 통해 1이상의 양수만 받아준다
- includes: 2글자 이상 10글자 이하만 가능, 특수문자는 불가능하고 한영, 숫자만 가능
public class QuestionsOption {
@ValidTags
private List<String> tags;
@Builder.Default
@Positive
private Integer count = 10;
@Pattern(regexp = "^[a-zA-z가-힣0-9]+$", message = "검색은 허용되는 문자 내에서 입력해주세요.")
@Size(min=2, max = 10, message = "검색은 최소 2자 이상 10자 이하로 입력해주세요.")
private String includes;
}
- QuestionController
- @Valid를 통해 적용시킨다
@RequestMapping("api/v1/exams/{examId}/questions")
public class QuestionsController {
private final QuestionsService questionsService;
@GetMapping
public QuestionsList getExamQuestions(@PathVariable @ValidExamId Long examId, @ModelAttribute @Valid QuestionsOption questionsOption) {
...이하 생략
}
- QuestionControllerTest
- Valid가 제대로 적용되었는지 테스트 해준다
Tags 테스트
mockMvc.perform(get("/api/v1/exams/{examId}/questions", existExamId)
.queryParam("tags", "상황")
.queryParam("tags", "없는태그"))
.andExpect(status().isBadRequest());
Count 테스트
mockMvc.perform(get("/api/v1/exams/{examId}/questions", existExamId)
.queryParam("count", String.valueOf(-3)))
.andExpect(status().isBadRequest());
Includes 테스트
mockMvc.perform(get("/api/v1/exams/{examId}/questions", existExamId)
.queryParam("count", String.valueOf(-3)))
.andExpect(status().isBadRequest());
전체 테스트 코드
@WebMvcTest(QuestionsController.class)
@Tag("basic_test")
class QuestionsControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private QuestionsService questionsService;
@MockBean
private ExamRepository examRepository;
@MockBean
private DriverLicenseQuestionsRepository driverLicenseQuestionsRepository;
private final Long existExamId = 1L;
private final List<String> existTags = List.of("상황", "법");
@BeforeEach
public void beforeTest() {
when(examRepository.existsById(existExamId)).thenReturn(true);
when(questionsService.findByDriverLicenseQuestions(any(), any())).thenReturn(new QuestionsList());
when(driverLicenseQuestionsRepository.existsByTags(existTags.get(0))).thenReturn(true);
when(driverLicenseQuestionsRepository.existsByTags(existTags.get(1))).thenReturn(true);
}
@Test
void validQuestionControllerTest() throws Exception {
mockMvc.perform(get("/api/v1/exams/{examId}/questions", existExamId)
.queryParam("tags", "상황")
.queryParam("tags", "법")
.queryParam("count", String.valueOf(3))
.queryParam("includes", "고속도로"))
.andExpect(status().isOk());
}
// 없는 태그로 요청을 보내면 400 Bad Request를 반환하는 테스트
@Test
void invalidTagsTest() throws Exception {
// Then
mockMvc.perform(get("/api/v1/exams/{examId}/questions", existExamId)
.queryParam("tags", "상황")
.queryParam("tags", "없는태그"))
.andExpect(status().isBadRequest());
mockMvc.perform(get("/api/v1/exams/{examId}/questions", existExamId)
.queryParam("tags", "상황")
.queryParam("tags", "법"))
.andExpect(status().isOk());
}
// 양수가 아닌 count로 요청을 보내면 400 Bad Request를 반환하는 테스트
@Test
void invalidCountTest() throws Exception {
// Then
mockMvc.perform(get("/api/v1/exams/{examId}/questions", existExamId)
.queryParam("count", String.valueOf(-3)))
.andExpect(status().isBadRequest());
mockMvc.perform(get("/api/v1/exams/{examId}/questions", existExamId)
.queryParam("count", String.valueOf(3)))
.andExpect(status().isOk());
}
// 글자수 및 빈칸 적용된 유효하지않은 inlucds 요청시 400 Bad Request를 반환하는 테스트
@Test
void invalidIncludesTest() throws Exception {
// Then
mockMvc.perform(get("/api/v1/exams/{examId}/questions", existExamId)
.queryParam("includes", "두 단어"))
.andExpect(status().isBadRequest());
mockMvc.perform(get("/api/v1/exams/{examId}/questions", existExamId)
.queryParam("includes", "한"))
.andExpect(status().isBadRequest());
mockMvc.perform(get("/api/v1/exams/{examId}/questions", existExamId)
.queryParam("includes", "한단어한글자이상"))
.andExpect(status().isOk());
}
}
출처
- https://dev-coco.tistory.com/123
- https://adjh54.tistory.com/77#5.%20Validation%20적용-1
'스프링' 카테고리의 다른 글
[AI] 이미지 생성시 DALLE에서 Stable Diffusion으로 AI모델 변경 with SpringAI (2) | 2025.02.02 |
---|---|
AWS S3와 IAM 생성: 스프링과의 연동 방법 및 이미지 저장 (0) | 2024.03.20 |
[스프링] 스프링 3.1이후에 디버그 로그가 안뜰경우, 로그레벨 조정 방법 (0) | 2024.01.29 |
[스프링] 혼자 구현하는 웹 서비스1: 프로젝트 세팅 (0) | 2024.01.08 |
Google Drive API를 활용한 이미지 업로드 및 Url반환(스프링 서비스단계 구현) (0) | 2023.08.26 |