[Spring] MultipartFile을 이용한 파일 업로드 테스트 (MultipartFile과 함께 전송되는 데이터를 DTO에 바인딩 시키기)

Kim Dae Hyun·2022년 1월 26일
3

TIL

목록 보기
93/93

모 기업의 채용과제 해결 중 만난 문제이다..

클라이언트로부터 여러 개 첨부파일과 제목, 내용, 일자 등의 데이터를 받아서 저장해야 하는 요구사항을 처리하고 있었다.

이전에 비슷한 것을 기능을 구현해 본 적이 있어서 자신있게 구현에 들어가고 바로 테스트 코드 작성에 들어갔다. 분명 이상한게 없다고 생각했는데 2시간 동안 에러와 싸웠다 ..


📌 에러..

요청을 받는 API는 아래와 같은 형태이다

    @PostMapping
    public ResponseEntity saveNotice(
            @RequestPart("notice") SaveNoticeDto saveNotice, @RequestPart(name = "file", required = false) List<MultipartFile> files)
    {
    	...
        
        return new ResponseEntity(HttpStatus.CREATED);
    }
public class SaveNoticeDto {

    private String title;
    private String content;
    private LocalDateTime endTime;
}

MultipartFormData 형태를 받기 위해 @RequestPart를 사용했다.

MultipartFormData는 각 파트마다 이름과 Content-Type을 갖는 것으로 알고 있었다.
때문에 위 코드를 MockMvc로 테스트 할 때 fileMockMultipartFile로 만들어서 보내주고 SaveNoticeDto에 필요한 값은 Dto로 만들고 Json으로 변환하여 MockPart를 이용해서 보내줄 생각이었다.

    @DisplayName("POST /notices 공지사항 등록 API 테스트")
    @Test
    void saveNoticeApiTest() throws Exception {
        MockMultipartFile multipartFile1 = new MockMultipartFile("file", "test.txt", "text/plain", "test file".getBytes(StandardCharsets.UTF_8) );
        MockMultipartFile multipartFile2 = new MockMultipartFile("file", "test2.txt", "text/plain", "test file2".getBytes(StandardCharsets.UTF_8) );

        SaveNoticeDto noticeDto = new SaveNoticeDto("title1", "content1", LocalDateTime.now().plusDays(7));

        mockMvc.perform(multipart("/notices")
                .file(multipartFile1)
                .file(multipartFile2)
                .part(new MockPart("notice", noticeDtoJson.getBytes(StandardCharsets.UTF_8))).contentType(MediaType.APPLICATION_JSON)
        )
                .andDo(print())
                .andExpect(status().isCreated());
}

머리에서 그린 테스트를 코드로 그대로 옮긴 것이다.
실행결과는 실패 ..

Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported]

처음보는 Content type에 대한 에러가 터졌고 그만할까 생각했다..

사실 이전에 다른 방식으로 위 기능을 동일하게 만든 적이 있다.
조금 다른 방식으로 구현한 것 관련 포스팅
코틀린으로 구현한 것이지만 내용은 거의 같다.
다른 점은 컨트롤러에서 MultipartFile외에 다른 값을 받을 때 Dto가 아닌 String과 같은 단일값? 객체타입 로 받은 것이다.

똑같은 방식으로 문제를 해결할 수 있었지만 꼭.. Dto에 바인딩시키고 싶었다.


📌 해결 !!

    @DisplayName("POST /notices 공지사항 등록 API 테스트")
    @Test
    void saveNoticeApiTest() throws Exception {
        MockMultipartFile multipartFile1 = new MockMultipartFile("file", "test.txt", "text/plain", "test file".getBytes(StandardCharsets.UTF_8) );
        MockMultipartFile multipartFile2 = new MockMultipartFile("file", "test2.txt", "text/plain", "test file2".getBytes(StandardCharsets.UTF_8) );

        SaveNoticeDto noticeDto = new SaveNoticeDto("title1", "content1", LocalDateTime.now().plusDays(7));
        String noticeDtoJson = mapper.writeValueAsString(noticeDto);
        MockMultipartFile notice = new MockMultipartFile("notice", "notice", "application/json", noticeDtoJson.getBytes(StandardCharsets.UTF_8));

        mockMvc.perform(multipart("/notices")
                        .file(multipartFile1)
                        .file(multipartFile2)
                        .file(notice)
                )
                .andDo(print())
                .andExpect(status().isCreated())
                .andReturn();
    }

첨부파일을 위한 MultipartFile 뿐만 아니라 함께 넘어가는 값 또한 Dto로 만들고 Json으로 변한한 뒤 MultipartFile로 만들어줬다.

profile
좀 더 천천히 까먹기 위해 기록합니다. 🧐

1개의 댓글

comment-user-thumbnail
2022년 10월 6일

덕분에 하나 배우고 갑니다! 감사해요

답글 달기