전통적으로 API 문서화를 위해 Swagger를 사용해왔다. Swagger는 어노테이션의 정보를 통해 API를 문서화해준다. 다만 문서화의 대상이 되는 모든 메서드와 클래스, 심지어 DTO에도 어노테이션을 일일이 달아주어야 해서 설정을 위한 코드가 길어지게 된다.
@ApiOperation(value = "회원 정보 API", tags = {"Member Controller"}) // (1)
@RestController
@RequestMapping("/v11/swagger/members")
@Validated
@Slf4j
public class MemberControllerSwaggerExample {
private final MemberService memberService;
private final MemberMapper mapper;
public MemberControllerSwaggerExample(MemberService memberService, MemberMapper mapper) {
this.memberService = memberService;
this.mapper = mapper;
}
// (2)
@ApiOperation(value = "회원 정보 등록", notes = "회원 정보를 등록합니다.")
// (3)
@ApiResponses(value = {
@ApiResponse(code = 201, message = "회원 등록 완료"),
@ApiResponse(code = 404, message = "Member not found")
})
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberDto.Post memberDto) {
Member member = mapper.memberPostToMember(memberDto);
member.setStamp(new Stamp()); // homework solution 추가
Member createdMember = memberService.createMember(member);
return new ResponseEntity<>(
new SingleResponseDto<>(mapper.memberToMemberResponse(createdMember)),
HttpStatus.CREATED);
}
...
...
// (4)
@ApiOperation(value = "회원 정보 조회", notes = "회원 식별자(memberId)에 해당하는 회원을 조회합니다.")
@GetMapping("/{member-id}")
public ResponseEntity getMember(
@ApiParam(name = "member-id", value = "회원 식별자", example = "1") // (5)
@PathVariable("member-id") @Positive long memberId) {
Member member = memberService.findMember(memberId);
return new ResponseEntity<>(
new SingleResponseDto<>(mapper.memberToMemberResponse(member))
, HttpStatus.OK);
}
...
...
}
문서화를 간편하게 도와주고, 위와 같은 설정으로 작성된 문서 명세를 가지고 요청을 보내 테스트를 바로 할 수 있다는 장점이 있지만, 여전히 내부 로직에 문서화만을 위한 코드가 많이 있는 것은 고려해야할 대상이다.
또한 속성값을 직접 적어주기 때문에 오탈자로 인해 실제 API와 명세서 스펙이 일치하지 않을 수도 있으므로 개발자가 신경써야 될 부분이 많아지게 된다.
Spring REST Docs는 테스트 케이스에서 테스트가 통과되면 설정한 정보를 토대로 문서화를 해주는 프레임워크이다. 다음과 같은 장점이 있다.
@WebMvcTest(MemberController.class)
@MockBean(JpaMetamodelMappingContext.class)
@AutoConfigureRestDocs
public class MemberControllerRestDocsTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private MemberService memberService;
@MockBean
private MemberMapper mapper;
@Autowired
private Gson gson;
@Test
public void postMemberTest() throws Exception {
// given
MemberDto.Post post = new MemberDto.Post("[email protected]",
"홍길동",
"010-1234-5678");
String content = gson.toJson(post);
MemberDto.response responseDto =
new MemberDto.response(1L,
"[email protected]",
"홍길동",
"010-1234-5678",
Member.MemberStatus.MEMBER_ACTIVE,
new Stamp());
// willReturn()이 최소 null은 아니어야 한다.
given(mapper.memberPostToMember(Mockito.any(MemberDto.Post.class)))
.willReturn(new Member());
given(memberService.createMember(Mockito.any(Member.class)))
.willReturn(new Member());
given(mapper.memberToMemberResponse(Mockito.any(Member.class))).willReturn(responseDto);
// when
ResultActions actions =
mockMvc.perform(
post("/v11/members")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(content)
);
// then
actions
.andExpect(status().isCreated())
.andExpect(jsonPath("$.data.email").value(post.getEmail()))
.andExpect(jsonPath("$.data.name").value(post.getName()))
.andExpect(jsonPath("$.data.phone").value(post.getPhone()))
.andDo(document("post-member", // =========== (1) API 문서화 관련 코드 시작 ========
getRequestPreProcessor(),
getResponsePreProcessor(),
requestFields(
List.of(
fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"),
fieldWithPath("name").type(JsonFieldType.STRING).description("이름"),
fieldWithPath("phone").type(JsonFieldType.STRING).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("스탬프 갯수")
)
)
)); // =========== (2) API 문서화 관련 코드 끝========
}
}