[Spring boot] multipart, form-data, requestPart 테스트 코드 작성하기
프로젝트 중 유저 프로필을 작성하는 부분에서 multipart data로 이미지와 json 형식의 data를 동시에 받는 API가 있었다. 이 부분의 테스트 코드를 작성하는 과정을 정리해볼 예정..
테스트를 진행할 Controller
먼저 Spring 서버의 Controller 부분이다.
// 유저 프로필 작성
@PostMapping("/user/profile")
public ResponseEntity<UserDto.ResponseDto> writeUserProfile(@RequestPart("profileImg") MultipartFile profileImg,
@RequestPart("vacImg") MultipartFile vacImg,
@RequestPart("requestDto") UserDto.ProfileRequestDto requestDto,
@AuthenticationPrincipal UserDetailsImpl userDetails){
return ResponseEntity.ok().body(userService.writeUserProfile(profileImg, vacImg, requestDto, userDetails.getUser().getId()));
}
jwt 인증을 통한 로그인 방식을 사용하고 있어, 이 부분은 인증이 필요한 부분이다. 따라서 테스트 코드에서 사전 실행으로 회원 가입, 로그인을 진행하여 token을 파싱하여 포함 시켜 전송해줬다.
컨트롤러를 간단하게 보면 @RequestPart로 MultipartFile 2개와 Json 형식의 Dto 1개를 받고 있다. 이렇게 다중 파일을 첨부할 때 테스트 코드에서는 어떻게 처리하는지 보자.
Postman 테스트
그 전에 먼저 Postman으로 테스트를 한 것을 보면..
헤더에는 다음과 같이 JWT token 정보를 포함시켜 보내줬고,
Body는 아래와 같이 구성했다.
Controller에서 지정해준 변수 이름에 맞추어 해당 정보들을 보내면 된다.
KEY 선택에서 형식은 다음과 같이 선택해주면 된다.
profileImg -> File
vacImg -> File
requestDto -> text
테스트 코드 작성하기
자 이제 테스트 코드를 작성해보자..! 먼저 테스트에 있어 나는 mockMvc와 restTemplate 2가지 방식을 그때그때 번갈아 가며 썼는데, 정확한 차이점을 찾아보니 Servlet Container를 쓰냐 안쓰냐의 차이라고 한다. mockMvc를 사용할 경우 클라이언트의 요청이 들어오는 Dispatcher Servlet을 안거치고 마치 요청이 들어온 것 처럼 처리하는 것이고, restTemplate의 경우는 Dispatcher Servlet을 거치는 클라이언트 요청을 보내는 것이라고 생각하면 된다!
이번엔 mockMvc를 사용해서 테스트코드를 만들었다.
유저 프로필 등록하기(POST) 테스트 코드
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@AutoConfigureMockMvc //MockMvc를 사용하기 위한 어노테이션
class UserControllerTest {
@Autowired
MockMvc mockMvc;
private static ObjectMapper objectMapper = new ObjectMapper();
private HttpHeaders headers;
private String token = "";
/*
회원가입...
로그인하고 token 설정해주기..
*/
@BeforeEach
public void setup() {
headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
}
@Test
@DisplayName("프로필 작성 성공")
void test4() throws Exception {
headers.set("Authorization", token);
String content = objectMapper.writeValueAsString(new UserDto.ProfileRequestDto("남","10대", "초보", "hihihi"));
MockMultipartFile file1 = new MockMultipartFile("profileImg", "empty.txt", "text/plain", "".getBytes());
MockMultipartFile file2 = new MockMultipartFile("vacImg", "empty.txt", "text/plain", "".getBytes());
MockMultipartFile file3 = new MockMultipartFile("requestDto", "jsondata", "application/json", content.getBytes(StandardCharsets.UTF_8));
mockMvc.perform(multipart("/user/profile")
.file(file1)
.file(file2)
.file(file3).headers(headers)
.accept(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8"))
.andExpect(status().isOk())
.andDo(print());
}
}
코드를 간략하게 보면 header에 토큰 값을 포함시켜 주고, ObjectMapper라는 것을 사용하여 json값을 String으로 만들어 준다. 파일을 만들때 타입을 application/json으로 지정해주면 json파일로 인식한다!!!
이미지 같은 경우는 일단은 빈 이미지로 주기 위해 그냥 내용이 없는 빈 파일로 구성하였다. -> 이미지를 등록하지 않았을 경우 서버에서 예외처리를 통해 자동으로 처리해준다!
MockMultipartFile로 변수명에 맞게 파일들을 생성한 후 포함시켜 전송하면 된다.
MockMvc 같은 경우 multipart request를 자동으로 해주고 있어 매우 간편하고 편리했다.
- multipart("요청을 보내고자 하는 url")
- .file(파일을 담은 변수)
- .headers(포함할 헤더)
- 기타 설정 정보들...
- .andExpect(기대되는 응답) -> 기대되는 응답의 경우 Http Status를 뜻하는데 옵션이 매우 많다. 200, 3xx, 4xx, 5xx 등 원하는 것으로 설정하면 된다.
- andDo(print()) -> 요청, 응답 정보를 콘솔에 출력해준다.
결과 출력
MockHttpServletRequest:
HTTP Method = POST
Request URI = /user/profile
Parameters = {}
Headers = [Content-Type:"multipart/form-data;charset=UTF-8", Authorization:"BEARER eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJFWFBJUkVEX0RBVEUiOjE2NDEyMzM0NTAsImlzcyI6InNwYXJ0YSIsIlVTRVJfTkFNRSI6ImJlb21pbjEyIn0.htWUrsBnft7x-yhv2sB1F9DxwkICbsXex3Jl4cfHcho", Accept:"application/json"]
Body = null
Session Attrs = {}
Handler:
Type = com.ppjt10.skifriend.controller.UserController
Method = com.ppjt10.skifriend.controller.UserController#writeUserProfile(MultipartFile, MultipartFile, ProfileRequestDto, UserDetailsImpl)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Content-Type:"application/json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"SAMEORIGIN"]
Content type = application/json
Body = {"username":"asdfg999","phoneNum":"01012341234","nickname":"ë²ë¯¼","profileImg":"No Post Image","vacImg":"No Post Image","gender":"ë¨","ageRange":"10ë","career":"ì´ë³´","selfIntro":"hihihi"}
Forwarded URL = null
Redirected URL = null
Cookies = []
유저 정보 수정하기(PUT) 테스트 코드
번외로 수정도 올려본다.. multipart 함수를 쓰면 무조건 post 요청으로 가서 수정의 경우 put method로 설정해놓아서 에러가 발생했다.
MockMultipartHttpServletRequestBuilder를 써서 PUT 요청으로 세팅해주었더니 해결되어서 여기에 올려본다..
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@AutoConfigureMockMvc //MockMvc를 사용하기 위한 어노테이션
class UserControllerTest {
@Autowired
MockMvc mockMvc;
private static ObjectMapper objectMapper = new ObjectMapper();
private HttpHeaders headers;
private String token = "";
/*
회원가입...
로그인하고 token 설정해주기..
*/
@BeforeEach
public void setup() {
headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
}
@Test
@DisplayName("프로필 수정 성공")
void test8() throws Exception {
headers.set("Authorization", token);
String content = objectMapper.writeValueAsString(new UserDto.UpdateRequestDto("diddl99!","avdkd", "초보", "hello world!"));
MockMultipartFile file1 = new MockMultipartFile("profileImg", "empty.txt", "text/plain", "".getBytes());
MockMultipartFile file2 = new MockMultipartFile("vacImg", "empty.txt", "text/plain", "".getBytes());
MockMultipartFile file3 = new MockMultipartFile("requestDto", "jsondata", "application/json", content.getBytes(StandardCharsets.UTF_8));
MockMultipartHttpServletRequestBuilder builder = multipart("/user/info");
builder.with(request -> {
request.setMethod("PUT");
return request;
});
mockMvc.perform(builder
.file(file1)
.file(file2)
.file(file3).headers(headers)
.accept(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8"))
.andExpect(status().isOk())
.andDo(print());
}
}
아까랑 비슷한데 MockMultipartHttpServletRequestBuilder라는 것을 사용해서 multipart("요청을 보내고자하는 url)의 request method를 PUT으로 세팅해주었다.
결과 출력
MockHttpServletRequest:
HTTP Method = PUT
Request URI = /user/info
Parameters = {}
Headers = [Content-Type:"multipart/form-data;charset=UTF-8", Authorization:"BEARER eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJFWFBJUkVEX0RBVEUiOjE2NDEyMzM0NTAsImlzcyI6InNwYXJ0YSIsIlVTRVJfTkFNRSI6ImJlb21pbjEyIn0.htWUrsBnft7x-yhv2sB1F9DxwkICbsXex3Jl4cfHcho", Accept:"application/json"]
Body = null
Session Attrs = {}
Handler:
Type = com.ppjt10.skifriend.controller.UserController
Method = com.ppjt10.skifriend.controller.UserController#updateUserInfo(MultipartFile, MultipartFile, UpdateRequestDto, UserDetailsImpl)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Content-Type:"application/json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"SAMEORIGIN"]
Content type = application/json
Body = {"username":"asdfg999","phoneNum":"01012341234","nickname":"avdkd","profileImg":"No Post Image","vacImg":"No Post Image","gender":"ë¨","ageRange":"10ë","career":"ì´ë³´","selfIntro":"hello world!"}
Forwarded URL = null
Redirected URL = null
Cookies = []
출력 결과를 보면 닉네임과 자기소개 부분이 잘 바뀐 것을 알 수 있다.
<Reference>
https://newbedev.com/how-to-put-multipart-form-data-using-spring-mockmvc