1. First Try - 403 에러
처음에는 파일 테스트를 할 때 이전에 MockMvc로 테스트할 때처럼
"/user/update"의 Http Method가 patch이므로
MockMvcRequestBuilders.patch()를 사용했었다.
하지만 @RequestPart의 MultipartFile를 테스트할 때는 MockMvcRequestBuilders.patch()가 아니라
MockMvcRequestBuilders.multipart()로 따로 multipart builder가 존재한다는 것을
구글링으로 깨닫고 해당 메소드를 사용하였다.
mockMvc.perform(
multipart("/user/update")
.file(generateMultipartFileImage())
.file(generatedUpdateDto)
.andExpect(status().isOk());
이렇게 실행한 결과, 403 에러가 발생했다.
모든 요청 시 accssToken을 넣어서 보내줘야 하는데,
accessToken을 헤더에 담지 않은 것이었다.
따라서, 헤더에 액세스 토큰을 담기 위해
MockHttpServletRequestBuilder.header()를 사용하려고 다음과 같이 구현했다.
mockMvc.perform(
multipart("/user/update")
.header(accessHeader, BEARER + accessToken))
.file(generateMultipartFileImage())
.file(generatedUpdateDto)
.andExpect(status().isOk());
이렇게 구현했더니, MockMvcRequestBuilders.file() 부분에
Cannot resolve method 'file' in 'ResultActions' 이라는 컴파일 에러가 발생했다.
따라서, 왜 이러한 오류가 발생하는지 코드를 살펴보게 되었다.
설명하기 전에, 등장하는 클래스들을 색깔로 정리해보았다.
1. MockMultipartHttpServletRequestBuilder (자식 객체)
2. MockMvcRequestBuilders
3. MockHttpServletRequestBuilder (부모 객체)
💻 MockMvcRequestBuilders.multipart()
public static MockMultipartHttpServletRequestBuilder multipart(String urlTemplate, Object... uriVariables) {
return new MockMultipartHttpServletRequestBuilder(urlTemplate, uriVariables);
}
위의 MockMvcRequestBuilders.multipart()를 보면,
반환 타입으로 MockMultipartHttpServletRequestBuilder를 생성해서 반환하고 있다.
이때, 구현한 코드에서 file()은 MockMultipartHttpServletRequestBuilder의 메소드이다.
따라서, MockMvcRequestBuilders.multipart().file()이 가능한 것이다.
여기서 잠시 MockMultipartHttpServletRequestBuilder와
MockMvcRequestBuilders의 관계를 살펴보면,
public class MockMultipartHttpServletRequestBuilder extends MockHttpServletRequestBuilder
MockMultipartHttpServletRequestBuilder가
MockHttpServletRequestBuilder를 상속받고 있다.
MockHttpServletRequestBuilder (부모) -> MockMultipartHttpServletRequestBuilde(자식)
💻 MockHttpServletRequestBuilder.header()
public MockHttpServletRequestBuilder header(String name, Object... values) {
addToMultiValueMap(this.headers, name, values);
return this;
}
여기서, MockHttpServletRequestBuilder.header()를 살펴보면,
MockMultipartHttpServletRequestBuilder를 반환 타입으로 반환하고 있다.
이를 토대로, 처음에 구현한 코드 순서일 때 반환 흐름을 살펴보자.
mockMvc.perform(
multipart("/user/update") // 1
.header(accessHeader, BEARER + accessToken)) // 2
.file(generateMultipartFileImage()) // 3
.file(generatedUpdateDto)
.andExpect(status().isOk());
1. 반환 타입 : MockMultipartHttpServletRequestBuilder (자식 객체)
MockMvcRequestBuilders.multipart() ->
MockMultipartHttpServletRequestBuilder (자식 객체) 반환
2-1. 현재 타입 : MockMultipartHttpServletRequestBuilder (자식 객체)
2-2. 반환 타입 : MockHttpServletRequestBuilder (부모 객체)
MockMultipartHttpServletRequestBuilder.header()
-> MockHttpServletRequestBuilder.header()인데,
MockHttpServletRequestBuilder가 부모객체이므로
현재 타입인 MockMultipartHttpServletRequestBuilder에서 header() 호출 가능
3-1. 현재 타입 : MockHttpServletRequestBuilder (부모 객체)
MockMultipartHttpServletRequestBuilder.file()
-> 현재 부모 객체인 MockHttpServletRequestBuilder이 타입인데,
자식 객체인 MockMultipartHttpServletRequestBuilder의 메소드인
file()을 사용하려고 하니 오류가 발생한다.
따라서, 오류가 발생하는 원인은 메소드 호출 순서에 있었다.
헤더 설정 순서는 상관이 없으므로 file()과 header()의 순서를 바꿔서 해결했다.
💡 메소드 호출 순서 변경으로 해결한 코드
mockMvc.perform(
multipart("/user/update")
.file(generateMultipartFileImage())
.file(generatedUpdateDto)
.header(accessHeader, BEARER + accessToken))
.andExpect(status().isOk());
1. 반환 타입 : MockMultipartHttpServletRequestBuilder (자식 객체)
MockMvcRequestBuilders.multipart() ->
MockMultipartHttpServletRequestBuilder (자식 객체) 반환
2-1. 현재 타입 : MockMultipartHttpServletRequestBuilder (자식 객체)
2-2. 반환 타입 : MockMultipartHttpServletRequestBuilder (자식 객체)
MockMultipartHttpServletRequestBuilder.file() ->
자기 자신의 메소드이므로 file() 호출 가능
3-1. 현재 타입 : MockMultipartHttpServletRequestBuilder (자식 객체)
MockHttpServletRequestBuilder.header() ->
현재 타입이 자식 객체이므로, 부모 객체인 MockHttpServletRequestBuilder의 header() 호출 가능
이렇게 메소드 호출 순서로 인한 오류는 고쳤지만,
2번째 에러가 발생했다.
2. Second Try - 405 Error
mockMvc.perform(
multipart("/user/update")
.header(accessHeader, BEARER + accessToken))
.file(generateMultipartFileImage())
.file(generatedUpdateDto)
.andExpect(status().isOk());
첫 번째 시도에서 헤더 호출 순서 변경으로 해결한 코드로 실행을 했는데,
405 Error가 발생했다.
해당 405 에러는 맞지 않는 HttpMethod로 요청을 보냈을 때 발생하는 에러인데,
생각해보니 따로 코드에서 HttpMethod 설정을 해주지 않았었다.
구글링을 해보니 기본으로 Multipart/form-data 요청은 POST로 가도록 설정되어 있었다.
내가 테스트하는 "/user/update"의 HttpMethod는 PATCH 메소드였기 때문에 405 Error가 발생했다.
💻 Multipart 요청에 HTTP 메소드 지정하기
어떻게 PATCH를 설정해줘야하는지 몰라서 구글링을 해봤는데,
MockMvc에서 Multipart 요청은 HTTP 메소드가 POST로 강제된다는 글을 보았다.
그래서 방법이 없나 하고 여러 글을 찾아보던 와중에,
SpringBoot 2.6.9, Spring 5.3.21부터 HttpMethod를 설정할 수 있게 개선되었다는 글을 보았다.
해당 글에서 나온 사용법을 참고해서 PATCH 메소드를 지정해주었다.
mockMvc.perform(
multipart(HttpMethod.PATCH, "/user/update")
.file(generateMultipartFileImage())
.file(generatedUpdateDto)
.header(accessHeader, BEARER + accessToken))
.andExpect(status().isOk());
MockMvcRequestBuilders.multipart()의 파라미터로
HttpMethod와, URL을 지정할 수 있게 개선되어 위와 같이 설정해줬다.
이 코드로 실행하니, 정상적으로 PATCH multipart/form-data 요청이 실행되었다!
💡최종 코드
mockMvc.perform(
multipart(HttpMethod.PATCH, "/user/update")
.file(generateMultipartFileImage())
.file(generatedUpdateDto)
.header(accessHeader, BEARER + accessToken))
.andExpect(status().isOk());
Reference
https://mangkyu.tistory.com/218
[HTTP] HTML 폼(form) 요청이 GET, POST만 가능하고 PATCH, PUT, DELETE은 불가능한 이유
최근에 Multipart/form-data 형태의 데이터를 다루어야 했는데, PATCH나 PUT는 요청이 정상적으로 처리되지 않는 것을 확인했습니다. 그래서 어째서인지 이유를 찾아 보게 되었고, 이번에는 관련 내용에
mangkyu.tistory.com
'Spring > 기타' 카테고리의 다른 글
[Spring] @ResponseBody VS ResponseEntity<T> : 무엇을 사용할까? (1) | 2023.04.16 |
---|---|
[Spring] JdbcTemplate 스프링 빈은 어떻게 자동으로 등록될까?(feat.DataSource) (4) | 2023.04.16 |
[Spring] @Valid 여러 검증 어노테이션 검증 순서 설정 방법 (1) | 2023.01.24 |
[Spring] Postman multipart/form-data 여러 개 파일 보내기 (0) | 2023.01.05 |
[Spring] SpringBoot 테스트 시 @WebMvcTest와 @SpringBootTest의 차이 (0) | 2022.11.15 |
1. First Try - 403 에러
처음에는 파일 테스트를 할 때 이전에 MockMvc로 테스트할 때처럼
"/user/update"의 Http Method가 patch이므로
MockMvcRequestBuilders.patch()를 사용했었다.
하지만 @RequestPart의 MultipartFile를 테스트할 때는 MockMvcRequestBuilders.patch()가 아니라
MockMvcRequestBuilders.multipart()로 따로 multipart builder가 존재한다는 것을
구글링으로 깨닫고 해당 메소드를 사용하였다.
mockMvc.perform(
multipart("/user/update")
.file(generateMultipartFileImage())
.file(generatedUpdateDto)
.andExpect(status().isOk());
이렇게 실행한 결과, 403 에러가 발생했다.
모든 요청 시 accssToken을 넣어서 보내줘야 하는데,
accessToken을 헤더에 담지 않은 것이었다.
따라서, 헤더에 액세스 토큰을 담기 위해
MockHttpServletRequestBuilder.header()를 사용하려고 다음과 같이 구현했다.
mockMvc.perform(
multipart("/user/update")
.header(accessHeader, BEARER + accessToken))
.file(generateMultipartFileImage())
.file(generatedUpdateDto)
.andExpect(status().isOk());
이렇게 구현했더니, MockMvcRequestBuilders.file() 부분에
Cannot resolve method 'file' in 'ResultActions' 이라는 컴파일 에러가 발생했다.
따라서, 왜 이러한 오류가 발생하는지 코드를 살펴보게 되었다.
설명하기 전에, 등장하는 클래스들을 색깔로 정리해보았다.
1. MockMultipartHttpServletRequestBuilder (자식 객체)
2. MockMvcRequestBuilders
3. MockHttpServletRequestBuilder (부모 객체)
💻 MockMvcRequestBuilders.multipart()
public static MockMultipartHttpServletRequestBuilder multipart(String urlTemplate, Object... uriVariables) {
return new MockMultipartHttpServletRequestBuilder(urlTemplate, uriVariables);
}
위의 MockMvcRequestBuilders.multipart()를 보면,
반환 타입으로 MockMultipartHttpServletRequestBuilder를 생성해서 반환하고 있다.
이때, 구현한 코드에서 file()은 MockMultipartHttpServletRequestBuilder의 메소드이다.
따라서, MockMvcRequestBuilders.multipart().file()이 가능한 것이다.
여기서 잠시 MockMultipartHttpServletRequestBuilder와
MockMvcRequestBuilders의 관계를 살펴보면,
public class MockMultipartHttpServletRequestBuilder extends MockHttpServletRequestBuilder
MockMultipartHttpServletRequestBuilder가
MockHttpServletRequestBuilder를 상속받고 있다.
MockHttpServletRequestBuilder (부모) -> MockMultipartHttpServletRequestBuilde(자식)
💻 MockHttpServletRequestBuilder.header()
public MockHttpServletRequestBuilder header(String name, Object... values) {
addToMultiValueMap(this.headers, name, values);
return this;
}
여기서, MockHttpServletRequestBuilder.header()를 살펴보면,
MockMultipartHttpServletRequestBuilder를 반환 타입으로 반환하고 있다.
이를 토대로, 처음에 구현한 코드 순서일 때 반환 흐름을 살펴보자.
mockMvc.perform(
multipart("/user/update") // 1
.header(accessHeader, BEARER + accessToken)) // 2
.file(generateMultipartFileImage()) // 3
.file(generatedUpdateDto)
.andExpect(status().isOk());
1. 반환 타입 : MockMultipartHttpServletRequestBuilder (자식 객체)
MockMvcRequestBuilders.multipart() ->
MockMultipartHttpServletRequestBuilder (자식 객체) 반환
2-1. 현재 타입 : MockMultipartHttpServletRequestBuilder (자식 객체)
2-2. 반환 타입 : MockHttpServletRequestBuilder (부모 객체)
MockMultipartHttpServletRequestBuilder.header()
-> MockHttpServletRequestBuilder.header()인데,
MockHttpServletRequestBuilder가 부모객체이므로
현재 타입인 MockMultipartHttpServletRequestBuilder에서 header() 호출 가능
3-1. 현재 타입 : MockHttpServletRequestBuilder (부모 객체)
MockMultipartHttpServletRequestBuilder.file()
-> 현재 부모 객체인 MockHttpServletRequestBuilder이 타입인데,
자식 객체인 MockMultipartHttpServletRequestBuilder의 메소드인
file()을 사용하려고 하니 오류가 발생한다.
따라서, 오류가 발생하는 원인은 메소드 호출 순서에 있었다.
헤더 설정 순서는 상관이 없으므로 file()과 header()의 순서를 바꿔서 해결했다.
💡 메소드 호출 순서 변경으로 해결한 코드
mockMvc.perform(
multipart("/user/update")
.file(generateMultipartFileImage())
.file(generatedUpdateDto)
.header(accessHeader, BEARER + accessToken))
.andExpect(status().isOk());
1. 반환 타입 : MockMultipartHttpServletRequestBuilder (자식 객체)
MockMvcRequestBuilders.multipart() ->
MockMultipartHttpServletRequestBuilder (자식 객체) 반환
2-1. 현재 타입 : MockMultipartHttpServletRequestBuilder (자식 객체)
2-2. 반환 타입 : MockMultipartHttpServletRequestBuilder (자식 객체)
MockMultipartHttpServletRequestBuilder.file() ->
자기 자신의 메소드이므로 file() 호출 가능
3-1. 현재 타입 : MockMultipartHttpServletRequestBuilder (자식 객체)
MockHttpServletRequestBuilder.header() ->
현재 타입이 자식 객체이므로, 부모 객체인 MockHttpServletRequestBuilder의 header() 호출 가능
이렇게 메소드 호출 순서로 인한 오류는 고쳤지만,
2번째 에러가 발생했다.
2. Second Try - 405 Error
mockMvc.perform(
multipart("/user/update")
.header(accessHeader, BEARER + accessToken))
.file(generateMultipartFileImage())
.file(generatedUpdateDto)
.andExpect(status().isOk());
첫 번째 시도에서 헤더 호출 순서 변경으로 해결한 코드로 실행을 했는데,
405 Error가 발생했다.
해당 405 에러는 맞지 않는 HttpMethod로 요청을 보냈을 때 발생하는 에러인데,
생각해보니 따로 코드에서 HttpMethod 설정을 해주지 않았었다.
구글링을 해보니 기본으로 Multipart/form-data 요청은 POST로 가도록 설정되어 있었다.
내가 테스트하는 "/user/update"의 HttpMethod는 PATCH 메소드였기 때문에 405 Error가 발생했다.
💻 Multipart 요청에 HTTP 메소드 지정하기
어떻게 PATCH를 설정해줘야하는지 몰라서 구글링을 해봤는데,
MockMvc에서 Multipart 요청은 HTTP 메소드가 POST로 강제된다는 글을 보았다.
그래서 방법이 없나 하고 여러 글을 찾아보던 와중에,
SpringBoot 2.6.9, Spring 5.3.21부터 HttpMethod를 설정할 수 있게 개선되었다는 글을 보았다.
해당 글에서 나온 사용법을 참고해서 PATCH 메소드를 지정해주었다.
mockMvc.perform(
multipart(HttpMethod.PATCH, "/user/update")
.file(generateMultipartFileImage())
.file(generatedUpdateDto)
.header(accessHeader, BEARER + accessToken))
.andExpect(status().isOk());
MockMvcRequestBuilders.multipart()의 파라미터로
HttpMethod와, URL을 지정할 수 있게 개선되어 위와 같이 설정해줬다.
이 코드로 실행하니, 정상적으로 PATCH multipart/form-data 요청이 실행되었다!
💡최종 코드
mockMvc.perform(
multipart(HttpMethod.PATCH, "/user/update")
.file(generateMultipartFileImage())
.file(generatedUpdateDto)
.header(accessHeader, BEARER + accessToken))
.andExpect(status().isOk());
Reference
https://mangkyu.tistory.com/218
[HTTP] HTML 폼(form) 요청이 GET, POST만 가능하고 PATCH, PUT, DELETE은 불가능한 이유
최근에 Multipart/form-data 형태의 데이터를 다루어야 했는데, PATCH나 PUT는 요청이 정상적으로 처리되지 않는 것을 확인했습니다. 그래서 어째서인지 이유를 찾아 보게 되었고, 이번에는 관련 내용에
mangkyu.tistory.com
'Spring > 기타' 카테고리의 다른 글
[Spring] @ResponseBody VS ResponseEntity<T> : 무엇을 사용할까? (1) | 2023.04.16 |
---|---|
[Spring] JdbcTemplate 스프링 빈은 어떻게 자동으로 등록될까?(feat.DataSource) (4) | 2023.04.16 |
[Spring] @Valid 여러 검증 어노테이션 검증 순서 설정 방법 (1) | 2023.01.24 |
[Spring] Postman multipart/form-data 여러 개 파일 보내기 (0) | 2023.01.05 |
[Spring] SpringBoot 테스트 시 @WebMvcTest와 @SpringBootTest의 차이 (0) | 2022.11.15 |