์ด๋ฒ์ ํ ๊ฒ์ 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์ ์๋ฌ ๋ฉ์ธ์ง๋ค์ด ์ ๋ค์ด๊ฐ ์๋ ๊ฒ์ ๋ณผ์๊ฐ ์๋ค.