개요
개인 프로젝트에서 multipart/form-data를 받는 수정 API 요청 메서드를 만들었고, 이를 테스트하는 과정에서 예상과 달리 405 Error가 발생해 테스트에 실패하였다. 왜 이런 문제가 생겼는지 알아보았고, 어떻게 해결하였는지 작성하려고 한다.
문제 발생
@PutMapping(path = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public CategoryResponse update(@PathVariable Long id,
@RequestParam(required = false) String name,
@RequestPart(required = false) MultipartFile iconFile) {
...
return categoryService.update(id, requestDto);
}
위의 코드는 Controller의 일부분으로, multipart/form-data를 Content-Type으로 받는 PUT API 요청 메서드이다.
String newName = "newCategory";
MockMultipartFile newIconFile = new MockMultipartFile(
"iconFile",
"newCategoryIcon.png",
"image/png",
"newCategoryIcon.png".getBytes());
ResultActions response = mockMvc.perform(multipart("/api/categories/{id}", category.getId())
.file(newIconFile)
.part(new MockPart("name", newName.getBytes(StandardCharsets.UTF_8))));
response.andExpect(status().isOk());
그리고 API 테스트하기 위해 위와 같이 테스트 코드를 작성했다. 200 OK 상태를 전달하며 테스트가 성공할 것이라고 예상했던 것과 달리, 아래와 같은 메시지와 함께 테스트에 실패했다.
원인은?
사실, 위의 테스트 코드에서 사용한 multipart() 메서드는 Spring RestDocs에서 제공하는 RestDocumentationRequestBuilders의 메서드이며, 테스트와 동시에 API 문서화도 함께 적용하기 위하여 사용했다. 해당 메서드를 더 자세히 살펴보자.
RestDocumentationRequestBuilders.multipart()
해당 매서드는 MockMvcRequestBuilders의 multipart() 메서드를 호출하고, 이의 반환 값을 MockMultipartHttpServletRequestBuilder로 캐스팅해 다시 반환한다. 여기서는 특별한 이유를 찾지 못했으니 MockMvcRequestBuilders의 multipart() 메서드를 살펴보자.
MockMvcRequestBuilders.multipart()
이 메서드는 매개변수로 전달받은 urlTemplate과 uriVariables를 바탕으로 새로운 MockMultipartHttpServletRequestBuilder 객체를 생성하고 반환한다. 마지막으로 해당 객체의 생성자를 살펴보자.
MockMultipartHttpServletRequestBuilder
결국, 해당 생성자는 같은 클래스의 오버로딩된 생성자를 호출하는데, 이때 HttpMethod를 POST로 전달하는 것을 확인할 수 있다.
그런데 다시 MockMvcRequestBuilders 클래스로 돌아가보면, 해당 클래스는 위에서 확인했던 multipart() 메서드 뿐만 아니라, HttpMethod를 매개변수로 전달받는 오버로딩된 다른 multipart() 메서드도 존재하는 것을 볼 수 있다.
그러면 왜 RestDocumentationRequestBuilders의 multipart() 메서드는 애초에 MockMvcRequestBuilders의 multipart() 호출할 때, HttpMethod를 전달받지 않는 메서드를 선택했을까?
정확한 이유는 아직 찾을 수 없었지만, HTML Form Data 전송 방식의 특징 때문이라고 생각한다.
multipart/form-data는 HTML Form Data 전송 방식에 사용되는 Content-Type이며, HTML Form Data 전송 방식은 GET과 POST만 지원한다는 HTTP 표준 스펙이 있다(이러한 이유에 대해서는 맨 아래에 참조한 블로그에 잘 설명되어 있으니 참고). 그래서 HTTP API 문서화를 도와주는 RestDocumentationRequestBuilders는 최대한 이런 표준을 준수해야 하기 때문에 위와 같이 다른 Http Method를 지원하는 multipart() 메서드를 따로 오버로딩하지 않은 것이라고 생각한다.
해결 방법
1. API 메서드 수정
@PostMapping(path = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public CategoryResponse update(@PathVariable Long id,
@RequestParam(required = false) String name,
@RequestPart(required = false) MultipartFile iconFile) {
...
return categoryService.update(id, requestDto);
}
첫번째 해결 방법은 API 요청 메서드가 PUT 요청 대신, POST 요청을 받도록 변경하는 것이다. 이렇게 하면 HTTP 표준 스펙을 준수하면서도 테스트와 API 문서화를 성공적으로 마칠 수 있다.
Reference
[HTTP] HTML 폼(form) 요청이 GET, POST만 가능하고 PATCH, PUT, DELETE은 불가능한 이유
최근에 Multipart/form-data 형태의 데이터를 다루어야 했는데, PATCH나 PUT는 요청이 정상적으로 처리되지 않는 것을 확인했습니다. 그래서 어째서인지 이유를 찾아 보게 되었고, 이번에는 관련 내용에
mangkyu.tistory.com
'Spring' 카테고리의 다른 글
[Spring] Pagination 기본값 설정하기 (0) | 2024.04.03 |
---|---|
[Spring] MultipartFile Bean Validation (0) | 2024.01.26 |
[Spring] MultipartFile 테스트하는 방법 (0) | 2024.01.17 |
[Spring] Spring REST Docs 상세 설정 (0) | 2024.01.02 |
[Spring] Filter에서 발생한 예외 핸들링하기 (1) | 2023.12.25 |