반응형
이번에 할 것은 Bad Request 응답 본문 만들기 이다.
EventController
코드를 살펴보면,
@PostMapping
public ResponseEntity createEvent(@RequestBody @Valid EventDto eventDto, Errors errors) { //@Valid 오류가 난다면, 의존성 추가 : spring-boot-starter-validation 을 해준다.
if (errors.hasErrors()) {
return ResponseEntity.badRequest().build();
}
eventValidator.validate(eventDto, errors);
if (errors.hasErrors()) {
return ResponseEntity.badRequest().build();
}
... 이하 생략 ...
@Valid로 검증을 해서 에러가 발생한다면, errors에 에러들을 담아주는데, 우리는 그걸 body에 담아서 리턴하면 된다.
@PostMapping
public ResponseEntity createEvent(@RequestBody @Valid EventDto eventDto, Errors errors) { //@Valid 오류가 난다면, 의존성 추가 : spring-boot-starter-validation 을 해준다.
if (errors.hasErrors()) {
return ResponseEntity.badRequest().body(errors);
}
eventValidator.validate(eventDto, errors);
if (errors.hasErrors()) {
return ResponseEntity.badRequest().body(errors);
}
... 이하 생략 ...
..
return ResponseEntity.created(createUri).body(event); //201응답을 Uri에 담아서 리턴시킨다.
테스트를 실행해보니 에러가 난다
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.validation.DefaultMessageCodesResolver and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.validation.BeanPropertyBindingResult["messageCodesResolver"])
에러를 살펴보면, No serializer found for class ... no properties discovered to create BeanSerializer 어쩌구..
해석을 해보면 errors를 json으로 serialize 하려 하니 -> validation을 위한 serialzier를 찾을수 없다는 말 같다.
에러가 나는 이유는?
BeanSerializer는 Bean으로 등록된 객체를 Json으로 직렬화 해주는데, event는 body에 넘겨도 에러가 안났다..
자바 빈 스펙을 준수하는 객체만 serialization이 가능한데, errors는 자바 빈 스펙을 준수하고 있지 않기 때문에 에러가 발생하는 것이다.
-> 이런경우는 우리가 직접 이 errors를 위한 Serializer를 직접 구현해준다.
커스텀 JSON Serializer 만들기
-
extends JsonSerializer<> (Jackson JSON 제공)
-
@JsonComponent(스프링 부트 제공)
/** * JsonComponent는 애노테이션을 붙여주면, 쉽게 등록 시켜준다. * 그러면 ,ObjectMapper가 errors라는 객체를 serialization 할때, 이 ErrorsSerializer를 사용한다. */ @JsonComponent public class ErrorsSerializer extends JsonSerializer<Errors> { @Override public void serialize(Errors errors, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException { /** * Error는 두가지 -> * FieldError : Validator에서 rejectValue( ... ) 를 하면 FieldError에 들어가게 된다. * GlobalError : "" reject(...)를 하면 GlobalError에 담기게 된다. */ gen.writeStartArray(); errors.getFieldErrors().forEach(e ->{ try { gen.writeStartObject(); gen.writeStringField("field", e.getField()); gen.writeStringField("objectName", e.getObjectName()); gen.writeStringField("code", e.getCode()); gen.writeStringField("defaultMessage", e.getDefaultMessage()); Object rejectedValue = e.getRejectedValue(); if (rejectedValue != null) { gen.writeStringField("rejectedValue", rejectedValue.toString()); } gen.writeEndObject(); } catch (IOException ioException) { ioException.printStackTrace(); } }); errors.getGlobalErrors().forEach(e ->{ try { gen.writeStartObject(); gen.writeStringField("object", e.getObjectName()); gen.writeStringField("code", e.getCode()); gen.writeStringField("defaultMessage", e.getDefaultMessage()); gen.writeEndObject(); } catch (IOException ioException) { ioException.printStackTrace(); } }); gen.writeEndArray(); } }
BindingError
- 두가지 : FieldError 와 GlobalError (ObjectError)가 있음
- objectName
- defaultMessage
- code
- field
- rejectedValue
Test에 코드 추가
@Test
@TestDescription("입력 값이 잘못된 경우에 에러가 발생하는 테스트")
public void createEvent_Bad_Request_Wrong_Input() throws Exception {
EventDto eventDto = EventDto.builder()
.name("Spring")
.description("REST API Development with Spring")
.beginEnrollmentDateTime(LocalDateTime.of(2020, 9, 8, 2, 45)) //시작 날짜가 끝나는 날짜보다 빠름!
.closeEnrollmentDateTime(LocalDateTime.of(2020, 9, 7, 2, 45))
.beginEventDateTime(LocalDateTime.of(2020, 9, 10, 2, 45))
.endEventDateTime(LocalDateTime.of(2020, 9, 9, 2, 45))
.basePrice(10000) //maxPrice 보다 큼
.maxPrice(200)
.limitOfEnrollment(100)
.location("Daejoen")
.build();
mockMvc.perform(post("/api/events")
.contentType(MediaTypes.HAL_JSON_VALUE)
.content(objectMapper.writeValueAsString(eventDto))
)
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$[0].objectName").exists()) // $[0] : 배열
.andExpect(jsonPath("$[0].field").exists()) // if, GlobalError 일땐, 에러가 난다(없는 값)
.andExpect(jsonPath("$[0].defaultMessage").exists())
.andExpect(jsonPath("$[0].code").exists())
.andExpect(jsonPath("$[0].rejectedValue").exists()) // if, GlobalError 일땐, 에러가 난다(없는 값)
;
}
테스트를 실행하면, Body에 에러 메세지들이 잘 들어가 있는 것을 볼수가 있다.
반응형