[Spring REST API #5] Bad Request 처리
- 📚 Spring/Spring REST
- 2020. 7. 30. 00:19
입력값이 이상한 경우 테스트 방법 즉, Dto의 값이 비어있을 경우 Bad Request 처리
- @Valid : Request에 들어있느 값들을 Dto에 바인딩할 때 검증을 수행할 수 있다.
package org.kyhslam.rest;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Builder @NoArgsConstructor @AllArgsConstructor
@Data
public class EventDto {
private String name;
private String description;
private LocalDateTime beginEnrollmentDateTime;
private LocalDateTime closeEnrollmentDateTime;
private LocalDateTime beginEventDateTime;
private LocalDateTime endEventDateTime;
private String location; // (optional)
private int basePrice; // (optional)
private int maxPrice; // (optional)
private int limitOfEnrollment;
}
package org.kyhslam.rest;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import java.net.URI;
@Controller
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_UTF8_VALUE)
public class EventController {
@Autowired
EventRepository eventRepository;
@Autowired
ModelMapper modelMapper;
@PostMapping
public ResponseEntity createEvent(@RequestBody EventDto eventDto){
Event event = modelMapper.map(eventDto, Event.class); // Dto를 event로 변환
Event newEvent = eventRepository.save(event);
URI createdUri = ControllerLinkBuilder.linkTo(EventController.class).slash(newEvent.getId()).toUri();
return ResponseEntity.created(createdUri).body(event);
}
}
테스트
package org.kyhslam.rest;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import java.time.LocalDateTime;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class EventControllerTests {
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
@Test
public void createEvent_Bad_Request_Empty_input() throws Exception {
EventDto eventDto = EventDto.builder().build();
this.mockMvc.perform(MockMvcRequestBuilders.post("/api/events")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(this.objectMapper.writeValueAsString(eventDto)))
.andExpect(MockMvcResultMatchers.status().isBadRequest());
}
}
입력값이 아무것도 없기 때문에 아래와 같은 201에러가 발생한다
결과
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/events
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8]}
Body = {"name":null,"description":null,"beginEnrollmentDateTime":null,"closeEnrollmentDateTime":null,"beginEventDateTime":null,"endEventDateTime":null,"location":null,"basePrice":0,"maxPrice":0,"limitOfEnrollment":0}
Session Attrs = {}
Handler:
Type = org.kyhslam.rest.EventController
Method = public org.springframework.http.ResponseEntity org.kyhslam.rest.EventController.createEvent(org.kyhslam.rest.EventDto)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 201
Error message = null
Headers = {Location=[http://localhost/api/events/1], Content-Type=[application/hal+json;charset=UTF-8]}
Content type = application/hal+json;charset=UTF-8
Body = {"id":1,"name":null,"description":null,"beginEnrollmentDateTime":null,"closeEnrollmentDateTime":null,"beginEventDateTime":null,"endEventDateTime":null,"location":null,"basePrice":0,"maxPrice":0,"limitOfEnrollment":0,"offline":false,"free":false,"eventStatus":"DRAFT"}
Forwarded URL = null
Redirected URL = http://localhost/api/events/1
Cookies = []
java.lang.AssertionError: Status
Expected :400
Actual :201
<Click to see difference>
입력값 제한을 위해 어떻게 처리를 해야하는가?
Dto와 Controller를 아래와 같이 변경한다
package org.kyhslam.rest;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@Builder @NoArgsConstructor @AllArgsConstructor
@Data
public class EventDto {
@NotEmpty
private String name;
@NotEmpty
private String description;
@NotNull
private LocalDateTime beginEnrollmentDateTime;
@NotNull
private LocalDateTime closeEnrollmentDateTime;
@NotNull
private LocalDateTime beginEventDateTime;
@NotNull
private LocalDateTime endEventDateTime;
private String location; // (optional)
@Min(0)
private int basePrice; // (optional)
@Min(0)
private int maxPrice; // (optional)
@Min(0)
private int limitOfEnrollment;
}
입력값이 없을 경우를 체크할 경우, @NotEmpty, @NotNull 어노테이션을 붙여서 이 프로퍼티가 값을 꼭 가져가야한다는 것을 명시한다. 또한, basePrice, maxPrice 같은 정수값을 산정할 때 최소값 제한을 두어 ( @Min(0) ) 해당 프로퍼티에 잘못된 값이 들어오는 것을 방지할 수 있다.
@PostMapping
public ResponseEntity createEvent(@RequestBody @Valid EventDto eventDto, Errors errors){
if(errors.hasErrors()){
return ResponseEntity.badRequest().build();
}
Event event = modelMapper.map(eventDto, Event.class); // Dto를 event로 변환
Event newEvent = eventRepository.save(event);
URI createdUri = ControllerLinkBuilder.linkTo(EventController.class).slash(newEvent.getId()).toUri();
return ResponseEntity.created(createdUri).body(event);
}
- EventDto에 @Valid 어노테이션을 붙이면 Request에 담겨진 데이터가 eventDto에 바인딩 될때 EventDto에 정의한 @NotNull 등 과 같은 정의들을 검사한다.
- 그리고 만약 에러가 발생한다면 그 옆에 정의한 Errors의 객체에 에러들의 내용들이 정의되어진다.
'📚 Spring > Spring REST' 카테고리의 다른 글
[Spring REST API #7] Bad Request 에 대한 본문 메세지 (0) | 2020.07.31 |
---|---|
[Spring REST API #6] Bad Request (도메인 Validator를 통한 처리) (0) | 2020.07.31 |
[Spring REST API #4] Spring REST API Bad Request 처리 (0) | 2020.07.28 |
[Spring REST API #3] Spring REST API 입력값 제한 및 에러 발생 처리 (0) | 2020.07.27 |
[Spring REST API #2] Spring REST API 클래스 생성 및 201 테스트 (0) | 2020.07.19 |