사이드 프로젝트를 하다가 파일 업로드 기능을 개발했다.
파일 업로드의 경우, Request 의 Content-Type 을 multipart/form-data 로 보내도록 했는데,
application/json 일 때, 문제가 없었던 한글깨짐 현상이 발생해서 테스트가 실패로 나오는 문제를 겪었다.
해당 문제를 해결한 과정을 적어보려고 한다.
1. 발생한 문제
보통 인수테스트를 작성할 때, 요청의 Content-Type(a.k.a MediaType, 구. Mime Type) 을 application/json 으로 보냈는데, 파일 업로드 기능을 테스트 하기 위해 요청의 Content-Type을 multipart/form-data 로 변경했다.
당연히, 그에 따라 Controller 의 파라미터에도 변화가 생겼는데, 이 과정에서 아래 처럼 한글이 깨지는 오류가 발생했다.
2. 원인 예상
원인을 파악해보기 위해 여러가지 확인을 하던 와중에 이상한 점을 발견하게 됐다. 아래 사진은 RestAssured 를 활용한 테스트 코드와 테스트 log 에 남은 테스트 요청이다.
public static ExtractableResponse<Response> 상품_등록_요청(String productName, int price, int count, Long categoryId, Long subCategoryId) {
ProductRequest productRequest = new ProductRequest(
productName, price, count, categoryId, subCategoryId, "상품 설명 입니다."
);
return RestAssured
.given().log().all()
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
.headers(createAuthorizationHeader(판매자_로그인토큰))
.multiPart("data", productRequest, MediaType.APPLICATION_JSON_VALUE)
.multiPart("file", "test.png", "".getBytes(StandardCharsets.UTF_8))
.when()
.post("/products")
.then().log().all()
.extract();
}
위 테스트 코드를 통해 인수테스트에서 요청을 발생시키면, 아래 사진과 같은 요청이 발생하게 됩니다.
여기서 한 가지 확인되는 내용은 multiparts 의 data 영역에 "한글 데이터가 정상적으로 보인다." 는 것 입니다.
그런데, 실제로 DB 에 Insert 할 때는 한글이 ??? 으로 바뀌어서 파라미터가 바인딩 되었다.
위 상황을 보고 "요청의 charset 과 서버의 charset 이 다른가?" 라고 생각하게 되어 해당 내용을 확인해보기로 했다.
3. 원인 파악
실제로 서버에 도달한 요청은 어떠한 정보를 가지고 있는지 log 로 출력해보았다.
여기서 문제가 되는 곳은 multiparts 중 에 data 영역 이므로, 해당 영역의 정보를 확인해보았다.
마침, 컨트롤러에서 사용하기 위해 커스텀한 ArgumentResolver가 있어서 log를 남겨보았다.
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if(request == null) {
throw new IllegalStateException("네트워크에 문제가 발생했습니다. 잠시 후, 다시 시도해주세요.");
}
/* 여기가 request 분석 시작 */
Part data = request.getPart("data");
String contentType = data.getContentType();
String name = data.getName();
log.info("request data name ==> {}", name);
log.info("request data contentType ==> {}", contentType);
/* 여기가 request 분석 끝 */
String token = parsingToken(request);
Long partnerId = jwtTokenProvider.getUserId(token);
return new AuthorizedPartner(partnerId);
}
위 처럼 log로 확인해본 결과, 아래 결과가 나타났다...!
확인한 multiparts 의 data 영역은 "application/json; charset=US-ASCII" 의 Content-Type을 가지고 있었다.
즉, UTF-8 이 아니기 때문에 한글이 깨졌을 것 이라고 예상할 수 있었다.
원인이 확인 됐으므로, 테스트 코드를 수정하러 가보자.
(실제 프론트엔드에선 charset 을 UTF-8로 맞춰 보내는 것이 확인된 상태이다.)
4. 테스트 코드 수정
RestAssured 를 활용했던 테스트 코드에서 charset 을 별도로 지정하진 않았었다.
평소에 작성했던 application/json 요청에서는 신경 쓸 필요가 없었어서 미쳐 생각하지 못했던 것 같다.(이제라도 알아서 다행....?)
테스트 코드를 아래처럼 수정했다.
public static ExtractableResponse<Response> 상품_등록_요청(String productName, int price, int count, Long categoryId, Long subCategoryId) {
ProductRequest productRequest = new ProductRequest(
productName, price, count, categoryId, subCategoryId, "상품 설명 입니다."
);
return RestAssured
.given().log().all()
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
.headers(createAuthorizationHeader(판매자_로그인토큰))
// 주석친 부분이 원래 코드
//.multiPart("data", productRequest, MediaType.APPLICATION_JSON_VALUE)
.multiPart("data", productRequest, "application/json; charset=UTF-8")
.multiPart("file", "test.png", "".getBytes(StandardCharsets.UTF_8))
.when().post("/products")
.then().log().all()
.extract();
}
charset 을 강제로 넣어준 형태이다.
원래는 MediaType.APPLICATION_JSON_UTF8_VALUE 라는 동일한 상수가 존재하지만, deprecated 되었다. ㅠ
이제 성공할 것이라고 생각했는데..... 테스트를 다시 실행해보니, 아래처럼 오류가 발생했다.
대충 해석해보면... "MIME type 에 characters 를 포함하면 안된다" 는 내용으로 보여진다.
즉, application/json 만 써야지, 뒤에다가 charset 을 붙이지 말라고 오류를 뱉은거 같다.
여기서 어떻게 하란 건지 막막했지만, 구글링을 통해 RestAssured 에서 공식적으로 운영하는 Github 를 방문해서 해결 방법을 찾을 수 있었다.
https://github.com/rest-assured/rest-assured/issues/1261
필자와 비슷한 이슈를 공유한 Github 링크인데, 해당 링크를 들어가보면 이슈내용과 유일하게 3개의 따봉을 받은 답변을 확인 할 수 있다.
(저도 눌렀습니다...ㅎ)
RestAssured 에서 multipart 메서드의 파라미터를 MultipartSpecBuilder 라는 객체를 넘기면서, 해당 객체에 Charset 을 지정할 수 있다는 답변이었다.
MultipartSpecBuilder 객체에 대한 자세한 사용법은 RestAssured 공식 Github에서 확인이 가능하다!
https://github.com/rest-assured/rest-assured/wiki/Usage
위에서 알아본 내용을 바로 적용해보았다.
public static ExtractableResponse<Response> 상품_등록_요청(String productName, int price, int count, Long categoryId, Long subCategoryId) {
ProductRequest productRequest = new ProductRequest(
productName, price, count, categoryId, subCategoryId, "상품 설명 입니다."
);
// Github 이슈에서 확인한 답변
MultiPartSpecification data =
new MultiPartSpecBuilder(productRequest, ObjectMapperType.JACKSON_2)
.controlName("data") // Multipart 영역의 이름
.mimeType(MediaType.APPLICATION_JSON_VALUE)
.charset("UTF-8") // 여기서 charset 지정...!
.build();
return RestAssured
.given().log().all()
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
.headers(createAuthorizationHeader(판매자_로그인토큰))
//주석친 부분이 수정 전
//.multiPart("data", productRequest, "application/json; charset=UTF-8")
.multiPart(data)
.multiPart("file", "test.png", "".getBytes(StandardCharsets.UTF_8))
.when().post("/products")
.then().log().all()
.extract();
}
위 처럼 수정하고 테스트를 다시 실행해보니, 테스트가 성공했다!
성공과 더불어, 이제 한글 데이터가 깨지지 않고 insert 된 것도 확인할 수 있었다.
마지막으로 아까 찍어놓은 요청의 log 를 확인해봤다.
아까 US-ASCII 였던 charset 이 이젠 UTF-8로 넘어오는 것이 확인됐다!
'Test Code' 카테고리의 다른 글
[Spring MVC] @WebMvcTest 에서 Multipart 테스트 하기. (0) | 2023.09.22 |
---|---|
@CsvSource annotation으로 여러 경우의 수 케이스 테스트하기 (0) | 2023.03.11 |
댓글