본문 바로가기
Test Code

[ATDD] RestAssured를 이용해 multipart/form-data 요청 테스트 시, 한글 깨지는 오류 해결하기

by 덩라 2023. 4. 22.

사이드 프로젝트를 하다가 파일 업로드 기능을 개발했다.

파일 업로드의 경우, 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 의 파라미터에도 변화가 생겼는데, 이 과정에서 아래 처럼 한글이 깨지는 오류가 발생했다.

DB insert 쿼리에 등장한 ?? (원래는 '육류' 라는 데이터...)
한글이 깨지면서 나타난 테스트 실패 결과

 

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();
    }

테스트에서 발생한 Request의 Header, Multiparts

위 테스트 코드를 통해 인수테스트에서 요청을 발생시키면, 아래 사진과 같은 요청이 발생하게 됩니다.

여기서 한 가지 확인되는 내용은 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 되었다. ㅠ

deprecated 되어버린 APPLICATION_JSON_UTF8_VALUE...

 

이제 성공할 것이라고 생각했는데..... 테스트를 다시 실행해보니, 아래처럼 오류가 발생했다.

 

대충 해석해보면... "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로 넘어오는 것이 확인됐다!

댓글