개발 환경
- Build : Gradle
- SpringBoot : 2.7.5
- Java : 11
- OS : Mac
요구사항
RestDocs API 문서화를 위해 MockMvc를 이용하여 Controller 테스트 코드를 작성해야 함.
테스트할 코드는 form-data로 이미지 파일과 텍스트를 받아 리소스를 수정하는 메서드(PATCH)
따라서 MockMvc.perform으로 진행할 때 multipart()를 사용해서 테스트를 진행해야 하는데, multipart()는 기본적으로 POST로 하드 코딩되어 있음.
// MockMvcRequestBuilders
/**
* Create a {@link MockMultipartHttpServletRequestBuilder} for a multipart request,
* using POST as the HTTP method.
* @param urlTemplate a URL template; the resulting URL will be encoded
* @param uriVariables zero or more URI variables
* @since 5.0
*/
public static MockMultipartHttpServletRequestBuilder multipart(String urlTemplate, Object... uriVariables) {
return new MockMultipartHttpServletRequestBuilder(urlTemplate, uriVariables);
}
// MockMultipartHttpServletRequestBuilder
/**
* Package-private constructor. Use static factory methods in
* {@link MockMvcRequestBuilders}.
* <p>For other ways to initialize a {@code MockMultipartHttpServletRequest},
* see {@link #with(RequestPostProcessor)} and the
* {@link RequestPostProcessor} extension point.
* @param urlTemplate a URL template; the resulting URL will be encoded
* @param uriVariables zero or more URI variables
*/
MockMultipartHttpServletRequestBuilder(String urlTemplate, Object... uriVariables) {
this(HttpMethod.POST, urlTemplate, uriVariables);
}
조건
// Controller
@PatchMapping(생략)
public ResponseEntity<?> patchArtwork(생략 ...
@ModelAttribute ArtworkRequestDto request) {
ArtworkResponseDto response = artworkService.updateArtwork(생략 ... request);
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
// ArtworkRequestDto
public class ArtworkRequestDto {
private MultipartFile image;
private String title;
private String content;
...
}
구현
방법 1. multipart에 HttpMethod 추가 (MockMvc로 테스트만 할 경우)
기본적으로 MockMvcRequestBuilders와 MockMultipartHttpServletRequestBuilder 클래스 내부에는 HttpMethod를 매개변수로 받을 수 있게 오버 로딩이 되어 있다.
따라서 자신이 요청할 HttpMethod를 입력 매개 변수 첫 번째 위치에 넣어주면 된다.
// MockMvcRequestBuilders
public static MockMultipartHttpServletRequestBuilder multipart(HttpMethod httpMethod, String urlTemplate, Object... uriVariables) {
return new MockMultipartHttpServletRequestBuilder(httpMethod, urlTemplate, uriVariables);
}
// MockMultipartHttpServletRequestBuilder
MockMultipartHttpServletRequestBuilder(HttpMethod httpMethod, String urlTemplate, Object... uriVariables) {
super(httpMethod, urlTemplate, uriVariables);
super.contentType(MediaType.MULTIPART_FORM_DATA);
}
이걸 이용하여 구현하면 다음과 같다.
public class ArtworkControllerTest {
@Autowired
private MockMvc mockMvc;
...
@Test
void patchArtworkTest() throws Exception {
MockMultipartFile image = new MockMultipartFile(
"image",
"image.png",
"image/png",
"<<image.png>>".getBytes());
String title = "수정된 제목";
String content = "수정된 설명";
...
ResultActions actions =
mockMvc.perform(
/* 가장 첫 번째 위치에 HttpMethod.PATCH를 추가하였다. */
MockMvcRequestBuilders.multipart(HttpMethod.PATCH, "/galleries/{gallery-id}/artworks/{artwork-id}", galleryId, artworkId)
.file(image)
.param("title", title)
.param("content", content)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.MULTIPART_FORM_DATA)
);
...
}
테스트 결과
요청 결과를 확인하니 PATCH로 요청한 것을 확인할 수 있다.
MockHttpServletRequest:
HTTP Method = PATCH
Request URI = /galleries/1/artworks/1
Parameters = {title=[수정된 제목], content=[수정된 설명]}
Headers = [Content-Type:"multipart/form-data;charset=UTF-8", Authorization:"Bearer (AccessToken)", Accept:"application/json"]
Body = null
...
방법 2. 오버라이딩 (RestDocs를 위해 테스트해야 하는 경우)
단순히 테스트만 할 경우는 방법 1 에서 설명한 방법으로 할 수 있지만, API문서화를 위해 RestDocs - document()를 작성하여 Test를 진행하면 아래와 같이 에러가 발생한다.
urlTemplate not found. If you are using MockMvc did you use RestDocumentationRequestBuilders to build the request?
java.lang.IllegalArgumentException: urlTemplate not found. If you are using MockMvc did you use RestDocumentationRequestBuilders to build the request?
...
위의 에러를 해결하기 위해 MockMvcRequestBuilders.multipart()가 아닌 RestDocumentationRequestBuilders.multipart()를 사용할 경우 HttpMethod를 입력 매개 변수로 받을 수 있게 오버 로딩이 되어 있지 않아 오류가 발생한다.
RestDocumentationRequestBuilders.multipart() 메서드를 사용하면서 POST외 다른 HttpMethod로 요청을 보낼 수 있는 방법은multipart() 메서드의 리턴 타입인MockMultipartHttpServletRequestBuilder 와 with() 메서드를 이용하여 미리 구현한 다음 사용해야 한다.
MockMultipartHttpServletRequestBuilder builder = RestDocumentationRequestBuilders.
multipart(요청보낼 uri);
builder.with(new RequestPostProcessor() {
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.setMethod(사용할 HttpMethod);
return request;
}
});
이 방법을 토대로 구현하면 다음과 같다.
public class ArtworkControllerTest {
@Autowired
private MockMvc mockMvc;
...
@Test
void patchArtworkTest() throws Exception {
MockMultipartFile image = new MockMultipartFile(
"image",
"image.png",
"image/png",
"<<image.png>>".getBytes());
String title = "수정된 제목";
String content = "수정된 설명";
...
MockMultipartHttpServletRequestBuilder builder =
RestDocumentationRequestBuilders.
multipart("/galleries/{gallery-id}/artworks/{artwork-id}", galleryId, artworkId);
builder.with(new RequestPostProcessor() {
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.setMethod("PATCH");
return request;
}
});
ResultActions actions =
mockMvc.perform(
/* 위에서 작성한 builder 사용 */
builder
.file(image)
.param("title", title)
.param("content", content)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.MULTIPART_FORM_DATA)
);
...
}
테스트
요청 결과를 통해 PATCH로 요청한 것을 확인할 수 있다.
MockHttpServletRequest:
HTTP Method = PATCH
Request URI = /galleries/1/artworks/1
Parameters = {title=[수정된 제목], content=[수정된 설명]}
Headers = [Content-Type:"multipart/form-data;charset=UTF-8", Authorization:"Bearer (AccessToken)", Accept:"application/json"]
Body = null
...
Reference
https://stackoverflow.com/questions/38571716/how-to-put-multipart-form-data-using-spring-mockmvc
How to PUT multipart/form-data using Spring MockMvc?
I have a controller's method with a PUT method, which receives multipart/form-data: @RequestMapping(value = "/putIn", method = RequestMethod.PUT) public Foo updateFoo(HttpServletRequest requ...
stackoverflow.com
'SpringBoot' 카테고리의 다른 글
[Trouble Shooting] Web server failed to start. Port 8080 was already in use (0) | 2022.12.02 |
---|---|
[SpringBoot] AWS S3 파일(이미지) 업로드 및 삭제하기 구현 (0) | 2022.11.28 |
[TroubleShooting] SpringBoot Controller Test - MockMvc 302 Found, 403 Forbidden (0) | 2022.11.24 |
[SpringDataJPA] 쿼리메서드 참조 객체의 필드 사용 (0) | 2022.11.17 |
[Trouble Shooting] MaxUploadSizeExceededException (0) | 2022.11.03 |