API 문서화 방법 중 대표적인 두가지는 Spring REST Docs
와 Swagger
이다. 둘 다 사용해본 결과 REST Docs는 프로덕션 코드에 영향을 주지 않는다는 점이 좋았고, Swagger는 문서가 깔끔하고 문서상에 API를 테스트할 수 있는 기능이 있어 좋았다.
이번 프로젝트에서는 둘의 장점을 살릴 수 있는 방법이 있어 적용 중이다.
해당 방법을 적용하고 나서 처음으로 POST, PUT 요청에 대한 문서화를 하게 됐다.
@PostMapping("/reports")
public ResponseEntity<Void> reportCreation(
@RequestPart(value = "facilityReport") FacilityReportRequest facilityReportRequest,
@RequestPart(value = "imageFile", required = false) MultipartFile multipartFile,
@Auth UserInfo userInfo
) {
facilityReportService.reportCreation(facilityReportRequest, multipartFile, userInfo);
return ResponseEntity.noContent().build();
}
새로운 편의시설에 대한 제보를 할 수 있는 기능
@PutMapping("/reports/{facilityId}")
public ResponseEntity<Void> reportModification(
@PathVariable final Long facilityId,
@RequestPart(value = "facilityReport") FacilityReportRequest facilityReportRequest,
@RequestPart(value = "imageFile", required = false) MultipartFile multipartFile,
@Auth UserInfo userInfo
) {
facilityReportService.reportModification(facilityId, facilityReportRequest, multipartFile, userInfo);
return ResponseEntity.noContent().build();
}
그리고 기존 편의시설 정보에 새로운 정보를 추가하거나 잘못된 정보를 수정/삭제하는 제보를 할 수 있는 기능을 추가하고, 문서화 단계로 넘어갔다.
@RequestPart
로 MultipartFile
과 Request DTO를 동시에 받을 수 있게 구성을 했는데, 문서화 과정에서 상당히 어려움을 겪었다.
처음에는 아래와 같이 post요청을 보내도록 작성하고 Body에 DTO와 이미지 파일을 전달하려고 했다.
mockMvc.perform(
RestDocumentationRequestBuilders.post(DEFAULT_URL + "/reports")
.content(objectMapper.writeValueAsString(facilityReport))
.header("Authorization", "Bearer accessToken")
)
테스트 실행 결과 Required part is not present
라는 에러 메시지를 받아서 Part는 다른 방법으로 처리해야 하는구나 했다.
RestDocumentationRequestBuilders
클래스를 살펴보니 multipart라는 메소드가 있었다.
Postman에서 요청을 보낼 때 처럼 이미지 파일은 Content-Type을 multipart/form-data
로, DTO는 application/json
으로 보내야 하는데, 따로 설정하는 방법을 찾기 힘들었다.
여러 블로그를 참고한 결과 file의 매개변수로 필요한 MockMultipartFile
을 생성하는 과정에서 contentType을 지정할 수 있다는 것을 알았다.
그래서 아래와 같이 테스트용 이미지 파일을 생성하는 메소드를 추가했다.
protected MockMultipartFile getMockMultipartFile() {
String name = "imageFile";
String contentType = "multipart/form-data";
String path = "test.jpg";
return new MockMultipartFile(name, path, contentType, path.getBytes(StandardCharsets.UTF_8));
}
그리고 나서 아래와 같이 Request DTO도 MockMultipartFile
로 변환했다. 여기서는 content-type을 application/json
으로 설정해줬다.
MockMultipartFile facilityReportToPart = new MockMultipartFile(
"facilityReport",
null,
"application/json",
objectMapper.writeValueAsString(facilityReport).getBytes(StandardCharsets.UTF_8)
);
MockMultipartFile
두개를 추가한 다음 테스트 실행 결과 MockHttpServletRequest
는 제대로 생성이 되는데 아래와 같은 오류가 발생했다.
Request DTO의 필드에 대한 설명을 문서에 추가하고 싶었는데, 요청/응답에 포함되지 않은 필드에 대해 문서화 코드를 추가하면 오류가 발생하도록 막아놓은 것 같다.
그래서 request Body에 직렬화한 request DTO를 추가했다. 그 결과 requestFields에 작성한 필드의 스니펫이 제대로 생성되는 것을 확인할 수 있었다.
HandlerMethodArgumentResolver
를 사용해 커스텀 어노테이션으로 사용자 인증 정보를 가져올 수 있도록 구현을 했다.
하지만 테스트 과정에서 해당 ArgumentResolver가 동작을 해서 인증 오류를 발생시켰다.
그래서 ArgumentResolver를 @MockBean으로 가져오고 supportsParameter
, resolveArgument
등의 오버라이딩 된 메소드를 조작하는 코드를 추가했다.
when(authArgumentResolver.supportsParameter((MethodParameter) notNull()))
.thenReturn(true);
when(authArgumentResolver.resolveArgument(
(MethodParameter) notNull(),
(ModelAndViewContainer) notNull(),
(NativeWebRequest) notNull(),
(WebDataBinderFactory) notNull()
)).thenReturn(userInfo);
수정 제보 API도 같은 방법으로 문서화를 했다. 하지만 다음과 같은 오류가 발생했다.
RestDocumentationRequestBuilders
의 multipart 메소드는 HTTP Method의 기본값이 POST인데 PUT 요청을 받는 메소드에 사용해서 그런 것이었다.
RestDocumentationRequestBuilders
의 multipart 메소드가 MockMultipartHttpServletRequestBuilder
를 반환하기 때문에 다음과 같이 커스텀 해주고
MockMultipartHttpServletRequestBuilder customRestDocumentationRequestBuilder =
(MockMultipartHttpServletRequestBuilder) multipart(HttpMethod.PUT, DEFAULT_URL+"/reports/{facilityId}", facilityId);
테스트를 실행한 결과오류에서 친절하게 RestDocumentationRequestBuilders
를 사용해야 한다고 알려줬다.
조금 더 찾아보니 같은 상황을 겪으신 분이 정리해놓은 블로그가 있어서 참고를 했다.
RestDocs를 위해 테스트해야 하는 경우 MockMultipartHttpServletRequestBuilder
에 with() 메소드를 사용해 POST 이외의 HTTP Method를 사용할 수 있도록 커스텀하는 방법을 알게되었다.
MockMultipartHttpServletRequestBuilder customRestDocumentationRequestBuilder =
RestDocumentationRequestBuilders.multipart(DEFAULT_URL+"/reports/{facilityId}", facilityId);
customRestDocumentationRequestBuilder.with(new RequestPostProcessor() {
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.setMethod("PUT");
return request;
}
});
mockMvc.perform(
customRestDocumentationRequestBuilder
.file(imageFile)
.file(facilityReportToPart)
.content(objectMapper.writeValueAsString(facilityReport))
.header("Authorization", "Bearer accessToken")
)
with() 메소드의 매개변수로 필요한 RequestPostProcessor
의 생성과 동시에 postProcessRequest를 오버라이딩해서 PUT을 사용하게끔 변경했다.
테스트가 모두 통과하는 것을 확인하고 API 문서를 생성한 결과 원하는 항목이 제대로 추가되었다!!
아쉬운 점은 파일을 Request body에 추가해서 업로드도 할 수 있도록 아래와 같이 restdocs.payload.FieldDescriptor
의 type() 메소드에 MultipartFile을 넣어 type을 지정했는데 빌드 과정에서 오류가 발생했다.
.requestFields(
fieldWithPath("imageFile").description("편의시설 사진").type(MultipartFile.class).optional(),
Swagger를 사용하면 MultipartFile 타입인 매개변수는 자동으로 string($binary)가 매핑되는 것 같은데 지금 사용한 방법으로는 방법을 찾지 못했다.
ePages Github 깃허브를 찾아보니 아직 Multipartfile은 지원하지 않는 것 같다.
https://wonkang.tistory.com/161?category=957132
https://techblog.woowahan.com/2597/
https://dadadamarine.github.io/java/spring/2019/04/26/spring-controller-test3.html
안녕하세요 혹시 이부분 전체 코드 확인이 가능할까요..!
동일한 문제로 해결을 못 하고 있습니다 ㅠ