MockMvc를 이용하여 API의 Json Response를 JsonPath 표현식을 사용해 검증하는 예제를 정리하였습니다.
테스트 예제를 보기 앞서 테스트에 필요한 기본적인 지식들을 간단히 서술 해보겠습니다.
의존성 추가
Spring Boot에 spring-boot-starter-test 의존성을 추가하여 테스트를 작성할 수 있습니다.
@WebMvcTest와 @MockBean을 이용한 테스트
@SpringBootTest
어노테이션을 사용하면 스프링이 관리하는 모든 빈을 등록시켜서 통합 테스트를 진행하기 때문에 무겁습니다.- 하지만
@WebMvcTest
는 web 레이어 관련 빈들만 등록하므로 비교적 가볍습니다. - web레이어 관련 빈들만 등록되므로 Service는 등록되지 않습니다. 따라서 가짜로 만들어줄 필요가 있습니다. → @MockBean 활용
@MockBean
직접 Service단까지 가서 DB에 값을 얻어오는게 아닌 Service를 Mocking하여 서비스까지 가지 않고 개발자가 직접 임의의 값을 리턴하여 테스트할 수 있습니다.
@MockBean
이란 어노테이션을 사용하면 기존에 있는 Bean을 MockBean으로 대체하여 개발자가 테스트할 때 값을 커스터마이징하기 쉬워집니다.
이제 테스트에 사용되는 Member 엔티티, MemberController, Response 시 사용될 DTO를 보겠습니다.
회원 Entity
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
@Embedded
private Address address;
@Embeddable
@Getter
public class Address {
private String city;
private String street;
private String zipcode;
public Address() {
}
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
}
}
회원 DTO
@Data
@Builder
public class MemberDTO {
private Long id;
private String username;
private Address address;
@Getter
public class Address {
private String city;
private String street;
private String zipcode;
public Address() {
}
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
}
}
회원 Controller
@RestController
public class MemberApiController {
@GetMapping("/api/test/members")
public List<MemberDTO> get() {
List<Member> findMembers = memberService.findMembers();
return findMembers.stream()
.map(
e -> MemberDTO.builder()
.id(e.getId())
.username(e.getUsername())
.address(e.getAddress())
.build())
.collect(Collectors.toList());
}
}
초기화 메소드 initEach() 작성
@WebMvcTest(controllers = MemberApiController.class)
public class MemberApiTest {
MockMvc mockMvc;
@MockBean
MemberService memberService;
@BeforeEach
void initEach() {
this.mockMvc = standaloneSetup(new MemberApiController())
.addFilter(new CharacterEncodingFilter("UTF-8", true))
.defaultRequest(get("/api/test/members").accept(MediaType.APPLICATION_JSON).characterEncoding("UTF-8"))
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.alwaysDo(print())
.build();
List<Member> memberList = new ArrayList<>();
Member member1 = new Member();
member1.setId(1L);
member1.setUsername("Kyeongho Yoo");
member1.setAddress(new Address("서울", "오솔길", "123-123"));
Member member2 = new Member();
member1.setId(2L);
member2.setUsername("Minho Yoo");
member2.setAddress(new Address("제주", "올레길", "123-123"));
Member member3 = new Member();
member1.setId(3L);
member3.setUsername("Cheolsu Kim");
member3.setAddress(new Address("부산", "가락길", "123-123"));
memberList.add(member1);
memberList.add(member2);
memberList.add(member3);
when(memberService.findMembers()).thenReturn(memberList);
}
}
- initEach() 메소드는 @BeforeEach을 사용해 각 테스트 메소드 실행 직전에 초기화 작업을 진행
- 각 테스트 당 mockMvc를 생성해 주입
- mockService에 findMembers() 호출 시 반환 값 설정
아래는 위와 같이 환경을 구성했을 때 {get} /api/test/members
호출 시 반환되는 Json Response입니다.
[
{
"id": 1,
"username": "Kyeongho Yoo",
"address": {
"city": "서울",
"street": "오솔길",
"zipcode": "123-123"
}
},
{
"id": 2,
"username": "Minho Yoo",
"address": {
"city": "제주",
"street": "올레길",
"zipcode": "123-321"
}
},
{
"id": 3,
"username": "Cheolsu Kim",
"address": {
"city": "부산",
"street": "가락길",
"zipcode": "123-123"
}
}
]
이제 jsonPath를 이용한 검증 예제와 Hamcrest가 제공하는 라이브러리로 작성한 간단한 예제들을 살펴 보겠습니다.
JsonPath 표현식 문법은 https://github.com/json-path/JsonPath를 참고해주세요.
테스트 메소드 예제 목록
- JsonPath에 해당하는 값이 존재하는지
- JsonPath에 해당하는 값이 존해하지 않는지
- JsonPath에 해당하는 값의 동일성 비교
- JsonPath로 가져온 값을 hamcrestMatcher로 검증하기
JsonPath에 해당하는 값이 존재하는지
@Test
public void exists() throws Exception {
String expectByUsername = "$.[?(@.username == '%s')]";
String addressByCity = "$..address[?(@.city == '%s')]";
this.mockMvc.perform(get("/api/test/members"))
.andExpect(jsonPath(expectByUsername, "Kyeongho Yoo").exists())
.andExpect(jsonPath(expectByUsername, "Minho Yoo").exists())
.andExpect(jsonPath(expectByUsername, "Cheolsu Kim").exists())
.andExpect(jsonPath(addressByCity, "서울").exists())
.andExpect(jsonPath(addressByCity, "제주").exists())
.andExpect(jsonPath(addressByCity, "부산").exists())
.andExpect(jsonPath("$..['username']").exists())
.andExpect(jsonPath("$[0]").exists())
.andExpect(jsonPath("$[1]").exists())
.andExpect(jsonPath("$[2]").exists());
}
- "$.[?(@.username == '%s')]" Json 표현식에 %s에 파라미터를 바인딩하여 검색할 수 있음
- 검색한 값이 존재하는 지 검증 할 수 있음
- "$..address[?(@.city == '%s')]"와 같이 JsonPath 표현식을 잘 조합하면 자식까지 검색 가능
- "$..['username']" 처럼 해당 요소 자체가 존재하는 지 검증 가능
- "$[0]" 처럼 몇 번째 요소가 존재 하는 지 검증 가능
JsonPath에 해당하는 값이 존해하지 않는지
@Test
public void doesNotExist() throws Exception {
this.mockMvc.perform(get("/api/test/members"))
.andExpect(jsonPath("$.[?(@.username == 'Kyeongho Yoooooooo')]").doesNotExist())
.andExpect(jsonPath("$.[?(@.username == 'Cheolsu Kiiiiiiim')]").doesNotExist())
.andExpect(jsonPath("$[3]").doesNotExist());
}
- doesNotExist()를 사용하면 JsonPath로 값을 색적하여 값이 존재하지 않는지 검증할 수 있음
JsonPath에 해당하는 값의 동일성 비교
@Test
public void equality() throws Exception {
this.mockMvc.perform(get("/api/test/members"))
.andExpect(jsonPath("$[0].username").value("Kyeongho Yoo"))
.andExpect(jsonPath("$[1].username").value("Minho Yoo"));
// Hamcrest matchers...
this.mockMvc.perform(get("/api/test/members"))
.andExpect(jsonPath("$[0].username").value(equalTo("Kyeongho Yoo")))
.andExpect(jsonPath("$[0].address.city").value(equalTo("서울")));
}
- JsonPath로 특정한 값을 동일성 비교하여 검증할 수 있음
JsonPath로 가져온 값을 hamcrestMatcher로 검증하기
@Test
public void hamcrestMatcher() throws Exception {
this.mockMvc.perform(get("/api/test/members"))
.andExpect(jsonPath("$[0].username", startsWith("Kyeong")))
.andExpect(jsonPath("$[0].username", endsWith("Yoo")))
.andExpect(jsonPath("$[0].username", containsString("ho Yo")))
.andExpect(jsonPath("$[0].username", is(in(Arrays.asList("Kyeongho Yoo", "Minho Yoo")))));
}
- startsWith(), endsWith(), containsString(), is(in()) 등 Hamcrest가 제공하는 검증 메소드를 사용하여 다양한 방법으로 값을 검증할 수 있다.
@Test
public void hamcrestMatcherWithParameterizedJsonPath() throws Exception {
String expectByUsername = "$[%s].username";
String addressByCity = "$[%s].address.city";
this.mockMvc.perform(get("/api/test/members"))
.andExpect(jsonPath(expectByUsername , 0).value(startsWith("Kyeong")))
.andExpect(jsonPath(expectByUsername , 2).value(endsWith("Kim")))
.andExpect(jsonPath(expectByUsername , 1).value(containsString("nho Yo")))
.andExpect(jsonPath(addressByCity , 1).value(is(in(Arrays.asList("서울", "제주", "부산")))));
}
- 위와 같이 Hamcrest 검증 메소드를 jsonPath().value()에 사용할 수도 있다.
Reference
- spring-framework/spring-test 테스트 예제 코드 Git
- Hamcrest 프레임워크 : hamcrest.org/JavaHamcrest/idnex
- JsonPath : https://github.com/json-path/JsonPath
'Study > spring' 카테고리의 다른 글
Spring Data JPA(1) - Spring Data JPA와 JpaRepository 인터페이스의 공통 기능 (0) | 2021.01.20 |
---|---|
JPA - OSIV(Open Session In View) 정리 (18) | 2021.01.18 |
자바 ORM 표준 JPA 프로그래밍(14) - 경로 표현식, 페치 조인, 다형성 쿼리, Named 쿼리, 벌크 연산 (0) | 2021.01.10 |
자바 ORM 표준 JPA 프로그래밍(13) - 조인, 서브 쿼리, 조건식, 컬렉션 식, 스칼라 식, CASE 식 (0) | 2021.01.07 |
자바 ORM 표준 JPA 프로그래밍(12) - JPQL 기본 문법과 파라미터 바인딩, 프로젝션, 페이징 (0) | 2021.01.06 |