스프링 기반 REST API 개발(inflearn)2 - 백기선
이벤트 생성 API 개발
이벤트 API 테스트 클래스 생성
스프링 부트 슬라이스 테스트
- @WebMvcTest
- MockMvc 빈을 자동 설정 해준다. 따라서 그냥 가져와서 쓰면 됨.
- 웹 관련 빈만 등록해 준다. (슬라이스)
MockMvc
- 스프링 MVC 테스트 핵심 클래스
- 웹 서버를 띄우지 않고도 스프링 MVC (DispatcherServlet)가 요청을 처리하는 과정을 확인할 수 있기 때문에 컨트롤러 테스트용으로 자주 쓰임.
테스트 할 것
- 입력값들을 전달하면 JSON 응답으로 201이 나오는지 확인.
- Location 헤더에 생성된 이벤트를 조회할 수 있는 URI 담겨 있는지 확인.
- id는 DB에 들어갈 때 자동생성된 값으로 나오는지 확인
- 입력값으로 누가 id나 eventStatus, offline, free 이런 데이터까지 같이 주면?
- Bad_Request로 응답 vs 받기로 한 값 이외는 무시
- 입력 데이터가 이상한 경우 Bad_Request로 응답
- 입력값이 이상한 경우 에러
- 비즈니스 로직으로 검사할 수 있는 에러
- 에러 응답 메시지에 에러에 대한 정보가 있어야 한다.
- 비즈니스 로직 적용 됐는지 응답 메시지 확인
- offline과 free 값 확인
- 응답에 HATEOAS와 profile 관련 링크가 있는지 확인.
- self (view)
- update (만든 사람은 수정할 수 있으니까)
- events (목록으로 가는 링크)
- API 문서 만들기
- 요청 문서화
- 응답 문서화
- 링크 문서화
- profile 링크 추가
Event 생성 API 구현 - 201 응답 받기
- @RestController
- @ResponseBody를 모든 메소드에 적용한 것과 동일하다.
- ResponseEntity를 사용하는 이유
- 응답 코드, 헤더, 본문 모두 다루기 편한 API
- Location URI 만들기
- HATEOS가 제공하는 linkTo(), methodOn() 사용
- 객체를 JSON으로 변환
- ObjectMapper 사용
테스트 할 것
- 입력값들을 전달하면 JSON 응답으로 201이 나오는지 확인.
- Location 헤더에 생성된 이벤트를 조회할 수 있는 URI 담겨 있는지 확인.
- id는 DB에 들어갈 때 자동생성된 값으로 나오는지 확인
Event 생성 API - EventRepository 구현
스프링 데이터 JPA
JpaRepository 상속 받아 만들기
Enum을 JPA 맵핑시 주의할 것
- 기본값인 ORDINAL은 숫자로 저장되는데 데이터가 꼬일 수 있다.
- @Enumerated(EnumType.STRING)
-
- Mockito를 사용해서 mock 객체를 만들고 빈으로 등록해 줌.
- 하지않으면 Repository는 Mock 객체기 때문에 save나 무엇을 하더라도 return 값은 null이 나온다.
- (주의) 기존 빈을 테스트용 빈이 대체 한다.
- Mockito를 사용해서 mock 객체를 만들고 빈으로 등록해 줌.
테스트 할 것
- 입력값들을 전달하면 JSON 응답으로 201이 나오는지 확인.
- Location 헤더에 생성된 이벤트를 조회할 수 있는 URI 담겨 있는지 확인.
- id는 DB에 들어갈 때 자동생성된 값으로 나오는지 확인
입력값 제한하기
입력값 제한
- id 또는 입력 받은 데이터로 계산해야 하는 값들은 입력을 받지 않아야 한다.
- EventDto 적용
DTO -> 도메인 객체로 값 복사
ModelMapper 사용 (DTO를 도메인으로 convert)
<dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>2.3.1</version> </dependency>
- 빈으로 등록후 사용해야한다.
- 참고 - ModelMapper Customizing
통합 테스트로 전환
- @WebMvcTest 빼고 다음 애노테이션 추가
- Repository @MockBean 코드 제거
테스트 할 것
- 입력값으로 누가 id나 eventStatus, offline, free 이런 데이터까지 같이 주면?
- Bad_Request로 응답 vs 받기로 한 값 이외는 무시 (선택)
입력값 이외에 에러 발생
ObjectMapper 커스터마이징
- spring.jackson.deserialization.fail-on-unknown-properties=true
- springboot에서 제공한다.
- springboot에서 제공한다.
Bad Request 처리하기
@valid 와 BindingResult (또는 Errors)
- BindingResult는 항상 @Valid 바로 다음 인자로 사용해야 함. (스프링 MVC)
- @NotNull, @NotEmpty, @Min, @Max, … 사용해서 입력값 바인딩할 때 에러 확인할 수 있음
도메인 Validator 만들기
- Validator 인터페이스 없이 만들어도 상관없음
- 빈으로 등록 후, 사용
테스트 설명 용 애노테이션 만들기
Bad Request 응답 본문 만들기
- errors 객체를 JSON으로 serialization이 안되기 때문에 body에 담아도 에러가 발생한다.
- errors는 자바빈 스펙을 준수하고 있는 객체가 아니다… -> beanSerializer를 사용할 수 없다.
- ErrorsSerilizer를 만들어서 문제를 해결 할 수 있다.
Error의 rejectValue와 reject의 차이점
reject : 글로벌 에러
rejectValue : field 에러
- 생성한 ErrorSerializer를 ObjectMapper에 등록한다.
- 스프링부트에서는 @JsonComponent를 제공한다. (ObjectMapper에 등록해준다.)
- 스프링부트에서는 @JsonComponent를 제공한다. (ObjectMapper에 등록해준다.)
매개변수를 이용한 테스트
테스트 코드 리팩토링
- 테스트에서 중복 코드 제거
- 매개변수만 바꾸면서 사용하고 싶을 때, JUnitParams를 사용하면 해결 할 수 있다.
- 라이브러리 추가
<dependency> <groupId>pl.pragmatists</groupId> <artifactId>JUnitParams</artifactId> <version>1.1.1</version> <scope>test</scope> </dependency>
- test Class 설정
@RunWith(JUnitParamsRunner.class) public class EventTest { .... }
- 사용
@Test @Parameters({ "0, 0, ture", "100, 0, false", "0, 100, false" }) public void testFree(int basePrice, int maxPrice, boolean isFree) {
}//given Event event = Event.builder() .basePrice(basePrice) .maxPrice(maxPrice) .build(); //when event.update(); //then assertThat(event.isFree()).isEqualTo(isFree);
- TypeSafe하게 사용하고 싶다면
@Test @Parameters(method = "parametersForTestFree") public void testFree(int basePrice, int maxPrice, boolean isFree) {
} private Object[] parametersForTestFree() { return new Object[] { new Object[] {0, 0, true}, new Object[] {100, 0 , false}, new Object[] {0, 100, false} }; }// given Event event = Event.builder() .basePrice(basePrice) .maxPrice(maxPrice) .build(); // when event.update(); // then assertThat(event.isFree()).isEqualTo(isFree);
- parametersFor
를 사용하면 method = "parametersFor"는 생략 가능하다.