테스트 할 것
- 입력값으로 누가 id나 eventStatus, offline, free 이런 데이터까지 같이 주면?
- Bad_Request 발생 (이번 테스트!) vs 받기로 한 값 이외는 무시
핵심 코드
ObjectMapper
커스터마이징
spring.jackson.deserialization.fail-on-unknown-properties = true
// deserialization : 역직렬화(JSON -> 객체) 를 하는데, unknown-properties가 있으면 -> fail-on 실패를 던진다.
application.properties
spring.jackson.deserialization.fail-on-unknown-properties=true
#왜 deserialization이냐면, (Json)EventDto를 (객체)Event에 넣어주는것이므로 비직렬화 이다.
# 객체 -> Json => Serialization
# Json -> 객체 => deSerialization
EventController.class
package me.iseunghan.demoinflearnrestapi.events;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
@Controller
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_VALUE)
public class EventController {
private final EventRepository eventRepository;
private final ModelMapper modelMapper;
//생성자가 하나만있고, 받아올 타입이 빈으로 등록되어있으면 autowired 생략 가능
public EventController(EventRepository eventRepository, ModelMapper modelMapper) {
this.eventRepository = eventRepository;
this.modelMapper = modelMapper;
}
@PostMapping
public ResponseEntity createEvent(@RequestBody EventDto eventDto) {
/*Event event = Event.builder()
.name(eventDto.getName())
...
.build(); 를 손쉽게 매핑해주는 ModelMapper를 사용하면 된다.*/
Event event = modelMapper.map(eventDto, Event.class);
Event newEvent = this.eventRepository.save(event);
//link를 생성할땐,
//HATEOAS가 제공하는 linkTo(), methodOn()을 사용
//HATEOAS가 제공하는 linkTo(), methodOn()을 사용 , 지금은 클래스레벨에 RequestMapping이 걸렸기때문에 methodOn 사용 X
URI createUri = linkTo(EventController.class).slash(newEvent.getId()).toUri();
return ResponseEntity.created(createUri).body(event); //201응답을 Uri에 담아서 리턴시킨다.
}
}
EventDTO.class
package me.iseunghan.demoinflearnrestapi.events;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
//id 또는 입력 받은 데이터로 계산해야 하는 값들은 EventDTO로 입력값을 제한을 한다.
//Controller에서 ModelMapper로 EventDTO 객체를 받아서 Event객체로 매핑해주는 작업을 할것이다.
@Data @Builder @AllArgsConstructor @NoArgsConstructor
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;
}
EventControllerTests.class
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class EventControllerTests {
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
// TODO 받기로 한 값이 아닐때 -> Bad_Request로 응답
//그대로 실행하면 에러 발생,
// properties에 : spring.jackson.deserialization.fail-on-unknown-properties=true 를 추가해주면, unknown properties가 들어오면 fail -> error 발생!
@Test
public void createEvent_BadRequest() throws Exception {
Event event = Event.builder()
.id(100) // unknown properties
.name("Spring")
.description("REST API Development with Spring")
.beginEnrollmentDateTime(LocalDateTime.of(2020, 9, 7, 2, 45))
.closeEnrollmentDateTime(LocalDateTime.of(2020, 9, 8, 2, 45))
.beginEventDateTime(LocalDateTime.of(2020, 9, 9, 2, 45))
.endEventDateTime(LocalDateTime.of(2020, 9, 10, 2, 45))
.basePrice(100)
.maxPrice(200)
.limitOfEnrollment(100)
.location("Daejoen")
.free(true)
.offline(false)
.eventStatus(EventStatus.PUBLISHED)
.free(true) // unknown properties
.offline(false) // unknown properties
.eventStatus(EventStatus.PUBLISHED) // unknown properties
.build();
mockMvc.perform(post("/api/events/")
.contentType(MediaType.APPLICATION_JSON)//본문 요청에 json을 담아서 보내고 있다고 알려줌.
.accept(MediaTypes.HAL_JSON)//HAL_JSON으로 받는다.
.content(objectMapper.writeValueAsString(event)))//요청 본문에 넣어준다. objectMapper로 event를 json으로 변환후
//이 부분에서 Controller에 @RequestBody로 넘기는 과정에서 EventDto에 modelmapping할때 unknown_properties인 값이 들어와서 테스트가 깨질것이다.
.andDo(print())//어떤 응답과 요청을 받았는지 확인가능.
.andExpect(status().isBadRequest())//badRequest요청이 들어왔는지?
;
}
이해하기
Event
객체에다가 build();
를 하는데 여기에는 EventDTO
에는 없는 값들도 같이 들어가있다.(id라던지, free,offline,eventStatus)
그 event객체를 가지고 .content(objectMapper.writeValueAsString(event))
로 json으로 변환해서 -> post로 요청을 할때,Event event = modelMapper.map(eventDto, Event.class);
여기서 eventDto 객체를 Event객체로 매핑을 할때,
Test에서 넘어온 JSON 데이터는 이렇다,
{
"id" : 100, // X EventDTO 에는 없는 프로퍼티
"name" : "Spring",
"description" : "...",
...
...
"eventStatus" : "EventStatus.PUBLISHED" // X EventDTO 에는 없는 프로퍼티
}
Event 값들을 EventDto 로 값들을 없는 값들은 무시하고 매핑시켜준다. 하지만 우리는 아까 application.properties
에서
우리가 커스터마이징 했던 spring.jackson.deserialization.fail-on-unknown-properties=true
로 인해, unknown-properties가 있으므로, 테스트가 깨지게 된다.
테스트 할 것
- 입력값으로 누가 id나 eventStatus, offline, free 이런 데이터까지 같이 주면?
- Bad_Request 발생 (이번 테스트!) vs 받기로 한 값 이외는 무시
핵심 코드
ObjectMapper
커스터마이징
spring.jackson.deserialization.fail-on-unknown-properties = true
// deserialization : 역직렬화(JSON -> 객체) 를 하는데, unknown-properties가 있으면 -> fail-on 실패를 던진다.
application.properties
spring.jackson.deserialization.fail-on-unknown-properties=true
#왜 deserialization이냐면, (Json)EventDto를 (객체)Event에 넣어주는것이므로 비직렬화 이다.
# 객체 -> Json => Serialization
# Json -> 객체 => deSerialization
EventController.class
package me.iseunghan.demoinflearnrestapi.events;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
@Controller
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_VALUE)
public class EventController {
private final EventRepository eventRepository;
private final ModelMapper modelMapper;
//생성자가 하나만있고, 받아올 타입이 빈으로 등록되어있으면 autowired 생략 가능
public EventController(EventRepository eventRepository, ModelMapper modelMapper) {
this.eventRepository = eventRepository;
this.modelMapper = modelMapper;
}
@PostMapping
public ResponseEntity createEvent(@RequestBody EventDto eventDto) {
/*Event event = Event.builder()
.name(eventDto.getName())
...
.build(); 를 손쉽게 매핑해주는 ModelMapper를 사용하면 된다.*/
Event event = modelMapper.map(eventDto, Event.class);
Event newEvent = this.eventRepository.save(event);
//link를 생성할땐,
//HATEOAS가 제공하는 linkTo(), methodOn()을 사용
//HATEOAS가 제공하는 linkTo(), methodOn()을 사용 , 지금은 클래스레벨에 RequestMapping이 걸렸기때문에 methodOn 사용 X
URI createUri = linkTo(EventController.class).slash(newEvent.getId()).toUri();
return ResponseEntity.created(createUri).body(event); //201응답을 Uri에 담아서 리턴시킨다.
}
}
EventDTO.class
package me.iseunghan.demoinflearnrestapi.events;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
//id 또는 입력 받은 데이터로 계산해야 하는 값들은 EventDTO로 입력값을 제한을 한다.
//Controller에서 ModelMapper로 EventDTO 객체를 받아서 Event객체로 매핑해주는 작업을 할것이다.
@Data @Builder @AllArgsConstructor @NoArgsConstructor
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;
}
EventControllerTests.class
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class EventControllerTests {
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
// TODO 받기로 한 값이 아닐때 -> Bad_Request로 응답
//그대로 실행하면 에러 발생,
// properties에 : spring.jackson.deserialization.fail-on-unknown-properties=true 를 추가해주면, unknown properties가 들어오면 fail -> error 발생!
@Test
public void createEvent_BadRequest() throws Exception {
Event event = Event.builder()
.id(100) // unknown properties
.name("Spring")
.description("REST API Development with Spring")
.beginEnrollmentDateTime(LocalDateTime.of(2020, 9, 7, 2, 45))
.closeEnrollmentDateTime(LocalDateTime.of(2020, 9, 8, 2, 45))
.beginEventDateTime(LocalDateTime.of(2020, 9, 9, 2, 45))
.endEventDateTime(LocalDateTime.of(2020, 9, 10, 2, 45))
.basePrice(100)
.maxPrice(200)
.limitOfEnrollment(100)
.location("Daejoen")
.free(true)
.offline(false)
.eventStatus(EventStatus.PUBLISHED)
.free(true) // unknown properties
.offline(false) // unknown properties
.eventStatus(EventStatus.PUBLISHED) // unknown properties
.build();
mockMvc.perform(post("/api/events/")
.contentType(MediaType.APPLICATION_JSON)//본문 요청에 json을 담아서 보내고 있다고 알려줌.
.accept(MediaTypes.HAL_JSON)//HAL_JSON으로 받는다.
.content(objectMapper.writeValueAsString(event)))//요청 본문에 넣어준다. objectMapper로 event를 json으로 변환후
//이 부분에서 Controller에 @RequestBody로 넘기는 과정에서 EventDto에 modelmapping할때 unknown_properties인 값이 들어와서 테스트가 깨질것이다.
.andDo(print())//어떤 응답과 요청을 받았는지 확인가능.
.andExpect(status().isBadRequest())//badRequest요청이 들어왔는지?
;
}
이해하기
Event
객체에다가 build();
를 하는데 여기에는 EventDTO
에는 없는 값들도 같이 들어가있다.(id라던지, free,offline,eventStatus)
그 event객체를 가지고 .content(objectMapper.writeValueAsString(event))
로 json으로 변환해서 -> post로 요청을 할때,Event event = modelMapper.map(eventDto, Event.class);
여기서 eventDto 객체를 Event객체로 매핑을 할때,
Test에서 넘어온 JSON 데이터는 이렇다,
{
"id" : 100, // X EventDTO 에는 없는 프로퍼티
"name" : "Spring",
"description" : "...",
...
...
"eventStatus" : "EventStatus.PUBLISHED" // X EventDTO 에는 없는 프로퍼티
}
Event 값들을 EventDto 로 값들을 없는 값들은 무시하고 매핑시켜준다. 하지만 우리는 아까 application.properties
에서
우리가 커스터마이징 했던 spring.jackson.deserialization.fail-on-unknown-properties=true
로 인해, unknown-properties가 있으므로, 테스트가 깨지게 된다.