오늘
@Test
public void getMemberTest() throws Exception {
// TODO 여기에 MemberController의 getMember() 핸들러 메서드 API 스펙 정보를 포함하는 테스트 케이스를 작성 하세요.
// given 테스트 준비
long memberId = 1L;
Member member = new Member("hgd@gmail.com", "홍길동", "010-1010-1111");
member.setStamp(new Stamp());
member.setMemberId(1L);
MemberDto.Response response = new MemberDto.Response(1L, "hgd@gmail.com", "홍길동",
"010-1010-1111", Member.MemberStatus.MEMBER_ACTIVE, new Stamp());
given(memberService.findMember(Mockito.anyLong()))
.willReturn(new Member());
given(mapper.memberToMemberResponse(Mockito.any(Member.class)))
.willReturn(response);
// when 테스트 대상
ResultActions actions =
mockMvc.perform(get("/v11/members/{memberId}", memberId)
.accept(MediaType.APPLICATION_JSON)
);
// then 테스트 결과
actions
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.memberId").value(member.getMemberId()))
.andExpect(jsonPath("$.data.email").value(member.getEmail()))
.andExpect(jsonPath("$.data.name").value(member.getName()))
.andExpect(jsonPath("$.data.phone").value(member.getPhone()))
.andDo(document("get-member",
getResponsePreProcessor(),
pathParameters(
parameterWithName("memberId").description("회원 식별자")
),
responseFields(
List.of(
fieldWithPath("data").type(JsonFieldType.OBJECT).description("결과 데이터"),
fieldWithPath("data.memberId").type(JsonFieldType.NUMBER).description("회원 식별자"),
fieldWithPath("data.email").type(JsonFieldType.STRING).description("이메일"),
fieldWithPath("data.name").type(JsonFieldType.STRING).description("이름"),
fieldWithPath("data.phone").type(JsonFieldType.STRING).description("핸드폰"),
fieldWithPath("data.memberStatus").type(JsonFieldType.STRING).
description("회원 상태: 활동중 / 휴면상태 / 탈퇴 상태"),
fieldWithPath("data.stamp").type(JsonFieldType.NUMBER).description("스탬프 갯수")
)
)));
}
given(memberService) 부분에서 willReturn(new Member()); 가 되는 이유는 그 다음 로직에서 member가 쓰이지 않기 때문이다. 정확히 말하면 그 다음 로직인 mapper.memberTo~ 부분도 Mock으로 지정해줘서 가짜 객체이기 때문에 안쓰인다고 생각하면 되겠다.
@Test
public void getMembersTest() throws Exception {
// TODO 여기에 MemberController의 getMembers() 핸들러 메서드 API 스펙 정보를 포함하는 테스트 케이스를 작성 하세요.
// given
Member member1 = new Member("hgd@gmail.com", "홍길동", "010-1111-1111");
member1.setStamp(new Stamp());
member1.setMemberStatus(Member.MemberStatus.MEMBER_ACTIVE);
Member member2 = new Member("spring@gmail.com", "스프링", "010-2222-2222");
member2.setStamp(new Stamp());
member2.setMemberStatus(Member.MemberStatus.MEMBER_ACTIVE);
Page<Member> pageMembers = new PageImpl<>(List.of(member1, member2), PageRequest.of(0, 10, Sort.by("memberId").descending()), 2);
List<MemberDto.Response> responses = List.of(
new MemberDto.Response(1L, "hgd@gmail.com", "홍길동", "010-1111-1111",
Member.MemberStatus.MEMBER_ACTIVE, new Stamp()),
new MemberDto.Response(2L, "spring@gmail.com", "스프링", "010-2222-2222",
Member.MemberStatus.MEMBER_ACTIVE, new Stamp())
);
given(memberService.findMembers(Mockito.anyInt(), Mockito.anyInt()))
.willReturn(pageMembers);
given(mapper.membersToMemberResponses(Mockito.anyList()))
.willReturn(responses);
// when
ResultActions actions =
mockMvc.perform(
get("/v11/members")
.param("page", "1")
.param("size", "10")
.accept(MediaType.APPLICATION_JSON)
);
// then
actions
.andExpect(status().isOk())
.andExpect(jsonPath("$.data").isArray())
.andDo(document("get-members",
getResponsePreProcessor(), // get은 요청 body가 없고, 응답 body만 있으니 ResponsePreProcessor를 적어줌.
requestParameters( // 요청할 때 URL에 작성하는 파라미터 값.
parameterWithName("page").description("페이지"),
parameterWithName("size").description("사이즈")
),
responseFields( // 응답 body(필드)
List.of( // 객체 이름과 같아야함.
fieldWithPath("data").type(JsonFieldType.ARRAY).description("결과 데이터").optional(), // 데이터가 한 건도 없을 경우 null 방지
fieldWithPath("data[].memberId").type(JsonFieldType.NUMBER).description("회원 식별자"),
fieldWithPath("data[].email").type(JsonFieldType.STRING).description("이메일"),
fieldWithPath("data[].name").type(JsonFieldType.STRING).description("이름"),
fieldWithPath("data[].phone").type(JsonFieldType.STRING).description("핸드폰"),
fieldWithPath("data[].memberStatus").type(JsonFieldType.STRING).description("회원 상태: 활동중 / 휴면 상태 / 탈퇴 상태"),
fieldWithPath("data[].stamp").type(JsonFieldType.NUMBER).description("스탬프 갯수"),
fieldWithPath("pageInfo").type(JsonFieldType.OBJECT).description("페이지 정보"),
fieldWithPath("pageInfo.page").type(JsonFieldType.NUMBER).description("페이지"),
fieldWithPath("pageInfo.size").type(JsonFieldType.NUMBER).description("사이즈"),
fieldWithPath("pageInfo.totalElements").type(JsonFieldType.NUMBER).description("전체 건 수"),
fieldWithPath("pageInfo.totalPages").type(JsonFieldType.NUMBER).description("총 페이지 수")
)
)));
}
@Getter
public class MultiResponseDto<T> {
private List<T> data;
private PageInfo pageInfo;
public MultiResponseDto(List<T> data, Page page) {
this.data = data;
this.pageInfo = new PageInfo(page.getNumber() + 1,
page.getSize(), page.getTotalElements(), page.getTotalPages());
}
}
@AllArgsConstructor
@Getter
public static class Response {
private long memberId;
private String email;
private String name;
private String phone;
private Member.MemberStatus memberStatus;
private Stamp stamp;
public String getMemberStatus() {
return memberStatus.getStatus();
}
public int getStamp() {
return stamp.getStampCount();
}
}
findAll같은 경우 Controller 에 보면 Page라는 인터페이스에서 service를 사용하고,
List에서 pageMembers에서 값을 가져온다.
그렇기에 Mockito의 willReturn 값으로 가짜 값을 넣어줘서는 안된다.
값을 리턴해줄 때 생각해야하는 부분
그 다음 로직에서 그 값을 사용하는가?
ResultActions actions =
mockMvc.perform(
get("/v11/members")
.param("page", "1")
.param("size", "10")
.accept(MediaType.APPLICATION_JSON)
);
이 부분에서 .param
은 controller에서
RequestParam으로 값을 받고 있기 때문에 넣어준 것.
Postman에서 Response 데이터를 보면 "data" : [
되어있고, 그 안에 {
해서 데이터가 들어있으며, "pageInfo"
는 또 따로 되어있는 것을 알 수 있다.
Json 에서는 여러건의 데이터들이 있을 경우 대괄호로 표현한다.
그래서
responseFields(
List.of(
fieldWithPath("data").type(JsonFieldType.ARRAY).description("결과 데이터").optional(),
fieldWithPath("data[].memberId").type(JsonFieldType.NUMBER).description("회원 식별자"),
fieldWithPath("data[].email").type(JsonFieldType.STRING).description("이메일"),
fieldWithPath("data[].name").type(JsonFieldType.STRING).description("이름"),
fieldWithPath("data[].phone").type(JsonFieldType.STRING).description("핸드폰"),
fieldWithPath("data[].memberStatus").type(JsonFieldType.STRING).description("회원 상태: 활동중 / 휴면 상태 / 탈퇴 상태"),
fieldWithPath("data[].stamp").type(JsonFieldType.NUMBER).description("스탬프 갯수"),
fieldWithPath("pageInfo").type(JsonFieldType.OBJECT).description("페이지 정보"),
fieldWithPath("pageInfo.page").type(JsonFieldType.NUMBER).description("페이지"),
fieldWithPath("pageInfo.size").type(JsonFieldType.NUMBER).description("사이즈"),
fieldWithPath("pageInfo.totalElements").type(JsonFieldType.NUMBER).description("전체 건 수"),
fieldWithPath("pageInfo.totalPages").type(JsonFieldType.NUMBER).description("총 페이지 수")
)
)));
}
이 부분에서 data[].memberId
를 해준것이다. 대괄호는 [] 배열이기에.
@Test
public void deleteMemberTest() throws Exception {
// given
long memberId = 1L;
Member member = new Member("hgd@naver.com", "홍길동", "010-1111-1111");
member.setMemberId(1L);
doNothing().when(memberService).deleteMember(memberId);
//when
ResultActions actions =
mockMvc.perform(delete("/v11/members/{member-id}", memberId)
// API 문서를 작성 할 때는 perform() 란에 string을 사용하자. 오류가 발생 할 수 있다.
);
//then
actions
.andExpect(status().isNoContent())
.andDo(document("delete-member",
pathParameters(
parameterWithName("member-id").description("회원 식별자")
)
));
여러가지 기능들이 있지만 한가지의 기능만 추가해보겠다.
필수값 여부
Patch에서 .optional()
을 사용했는데 이런 경우 API문서에 해당 필드가 필수인지 아닌지를 적어주면 좋을 것이다.
Request-fields의 스니펫을 사용하려면
src/test/resources/org/springframework/restdocs/templates.request-fields.snippet
파일을 추가하자.
스니펫은 mustache 문법을 사용한다.
내용은 아래 사진 참고. |필드명 적고 | 타입 적고 | 필수값 여부(true / false) | 설명
이렇게 작성해준 뒤 API 문서를 보면
필수값 여부가 나타나는 것을 볼 수 있다.