๐Ÿ’ Spring/Spring REST API

6) [test] Bad_Request ์‘๋‹ต ๋ณธ๋ฌธ ๋งŒ๋“ค๊ธฐ - ์Šคํ”„๋ง REST API

2020. 9. 12. 16:28
๋ชฉ์ฐจ
  1. ์ปค์Šคํ…€ JSON Serializer ๋งŒ๋“ค๊ธฐ
  2. Test์— ์ฝ”๋“œ ์ถ”๊ฐ€
๋ฐ˜์‘ํ˜•


 

์ด๋ฒˆ์— ํ•  ๊ฒƒ์€ 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์— ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋“ค์ด ์ž˜ ๋“ค์–ด๊ฐ€ ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ์ˆ˜๊ฐ€ ์žˆ๋‹ค.

๋ฐ˜์‘ํ˜•
์ €์ž‘์žํ‘œ์‹œ (์ƒˆ์ฐฝ์—ด๋ฆผ)
  1. ์ปค์Šคํ…€ JSON Serializer ๋งŒ๋“ค๊ธฐ
  2. Test์— ์ฝ”๋“œ ์ถ”๊ฐ€
'๐Ÿ’ Spring/Spring REST API' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • [HAEOAS] linkTo ๋ฉ”์†Œ๋“œ exception ๋ฐœ์ƒ
  • 7) [test] @JUnitParams ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง - ์Šคํ”„๋ง REST API
  • 5) [test] ์ž…๋ ฅ๊ฐ’ ์ œํ•œํ•˜๊ธฐ (๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์œผ๋กœ ๊ฒ€์‚ฌ) - ์Šคํ”„๋ง REST API
  • 4) [test] ์ž…๋ ฅ๊ฐ’ ์ œํ•œํ•˜๊ธฐ (Bad_Request ๋ฐœ์ƒ) - ์Šคํ”„๋ง REST API
iseunghan
iseunghan
๊พธ์ค€ํ•˜๊ฒŒ ์—ด์‹ฌํžˆ..
iseunghan
iseunghan

๊ณต์ง€์‚ฌํ•ญ

  • ์–ด์ œ๋ณด๋‹ค ๋‚˜์€ ์˜ค๋Š˜์ด ๋˜๊ธฐ ์œ„ํ•ด ๐Ÿ”ฅ
  • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (261)
    • ๐Ÿ’ Spring (14)
      • ๊ฐœ๋… ๋ฐ ์ดํ•ด (2)
      • Spring ํ•ต์‹ฌ ๊ธฐ์ˆ  (24)
      • Spring REST API (8)
      • Spring MVC, DB ์ ‘๊ทผ ๊ธฐ์ˆ  (7)
      • Spring Security (23)
      • Spring in Action (1)
    • ๐ŸŒป JAVA (84)
      • ์ž๋ฐ” ORM ํ‘œ์ค€ JPA ํ”„๋กœ๊ทธ๋ž˜๋ฐ (20)
      • ์•Œ๊ณ ๋ฆฌ์ฆ˜, ์ž๋ฃŒ๊ตฌ์กฐ (13)
      • ๋””์ž์ธ ํŒจํ„ด (7)
      • ์ •๋ฆฌ์ •๋ฆฌ์ •๋ฆฌ (43)
      • JUnit (1)
    • ๐Ÿ”– Snippets (3)
      • Javascript (3)
    • โš™๏ธ Devops (22)
      • โ› Git (11)
      • ๐Ÿณ Docker (6)
      • ๐Ÿง Linux (3)
      • ๐ŸŒˆ Jenkins (1)
      • ๐Ÿ“ฌ Kafka (1)
    • ๐Ÿ’ฌ ETC.. (4)
      • ๐Ÿ’ป macOS (2)
    • ๐ŸŒง๏ธ ORM (2)
      • JPA (2)
    • ๐Ÿ Python (2)
    • ๐Ÿ“š Databases (15)
      • ์˜ค๋ผํด๋กœ ๋ฐฐ์šฐ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐœ๋ก ๊ณผ ์‹ค์Šต(2ํŒ) (3)
      • RealMySQL 8.0 (8)
    • ๐Ÿ”ฅ Computer Science (5)
      • ๐Ÿ“ก ๋„คํŠธ์›Œํฌ (5)
    • ๐Ÿท๏ธ ํ˜‘์—… (1)
    • ๐Ÿ“œ ์ฝ”๋”ฉํ…Œ์ŠคํŠธ (38)
      • BAEKJOON\์ˆ˜ํ•™ 1, ์ˆ˜ํ•™ 2 (8)
      • BAEKJOON\์žฌ๊ท€ (5)
      • BAEKJOON\๋ธŒ๋ฃจํŠธ ํฌ์Šค (3)
      • BAEKJOON\์ •๋ ฌ (1)
      • BAEKJOON\๋ฐฑํŠธ๋ž˜ํ‚น (5)
      • BAEKJOON\BFS, DFS (6)
      • BAEKJOON\์ด๋ถ„ํƒ์ƒ‰ (1)
      • BAEKJOON\๋‹ค์ด๋‚˜๋ฏน ํ”„๋กœ๊ทธ๋ž˜๋ฐ (9)
      • BAEKJOON\๊ทธ๋ฆฌ๋”” ์•Œ๊ณ ๋ฆฌ์ฆ˜ (0)
    • โœจ ISEUNGHAN (1)

์ธ๊ธฐ ๊ธ€

์ตœ๊ทผ ๊ธ€

์ „์ฒด
์˜ค๋Š˜
์–ด์ œ
๋ฐ˜์‘ํ˜•
hELLO ยท Designed By ์ •์ƒ์šฐ.
iseunghan
6) [test] Bad_Request ์‘๋‹ต ๋ณธ๋ฌธ ๋งŒ๋“ค๊ธฐ - ์Šคํ”„๋ง REST API
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”

๊ฐœ์ธ์ •๋ณด

  • ํ‹ฐ์Šคํ† ๋ฆฌ ํ™ˆ
  • ํฌ๋Ÿผ
  • ๋กœ๊ทธ์ธ

๋‹จ์ถ•ํ‚ค

๋‚ด ๋ธ”๋กœ๊ทธ

๋‚ด ๋ธ”๋กœ๊ทธ - ๊ด€๋ฆฌ์ž ํ™ˆ ์ „ํ™˜
Q
Q
์ƒˆ ๊ธ€ ์“ฐ๊ธฐ
W
W

๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๊ธ€

๊ธ€ ์ˆ˜์ • (๊ถŒํ•œ ์žˆ๋Š” ๊ฒฝ์šฐ)
E
E
๋Œ“๊ธ€ ์˜์—ญ์œผ๋กœ ์ด๋™
C
C

๋ชจ๋“  ์˜์—ญ

์ด ํŽ˜์ด์ง€์˜ URL ๋ณต์‚ฌ
S
S
๋งจ ์œ„๋กœ ์ด๋™
T
T
ํ‹ฐ์Šคํ† ๋ฆฌ ํ™ˆ ์ด๋™
H
H
๋‹จ์ถ•ํ‚ค ์•ˆ๋‚ด
Shift + /
โ‡ง + /

* ๋‹จ์ถ•ํ‚ค๋Š” ํ•œ๊ธ€/์˜๋ฌธ ๋Œ€์†Œ๋ฌธ์ž๋กœ ์ด์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ํ‹ฐ์Šคํ† ๋ฆฌ ๊ธฐ๋ณธ ๋„๋ฉ”์ธ์—์„œ๋งŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.