ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링부트] 이미지 업로드 테스트 postman + restdocs
    BackEnd/SpringBoot 2023. 12. 3. 19:44

    1. RequestPart

    Content-Type이 multipart/form-data로 오는 요청에 대해 동작하며

    MultipartResolver 를 통해서 multipartFile을 받을 수있고 동시에 HttpMessageConverter를 통해서 RequestBody도 수행함을 알 수있다.

     

    공식문서 : 

    https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestPart.html

     

    RequestPart (Spring Framework 6.1.1 API)

    Annotation that can be used to associate the part of a "multipart/form-data" request with a method argument. Supported method argument types include MultipartFile in conjunction with Spring's MultipartResolver abstraction, jakarta.servlet.http.Part in conj

    docs.spring.io

     

     

    2. 테스트 대상 컨트롤러

    multipartFile : thumbnailImg, roomImgList

    requestbody : appRoomView

    @LoginCompanyCheck
    @PostMapping("/create")
    public JsonResponse<AppRoomDTO> appCompanyCreate(@CurrentCompany LoginCompanyInfo loginCompanyInfo,
                                                        @RequestPart(value = "appRoomView") AppRoomView appRoomView,
                                                        @RequestPart(value = "thumbnailImg", required = false) MultipartFile thumbnailImg,
                                                        @RequestPart(value = "roomImgList", required = false) List<MultipartFile> roomImgList)
            throws UserNotFoundException {
    
        AppRoomDTO appRoomDTO = appRoomService.companyRoomCreate(loginCompanyInfo, appRoomView, thumbnailImg, roomImgList);
    
        return JsonResponse.success(appRoomDTO);
    }

     

    3. Postman

    3.1 Body

    json 객체의 경우 다음과같이 작성한다.

    key : controller key와 동일하게 설정

    value : json 필드 동일하게 설정

    content-Type : application/json 입력

     

    Postman Body

     

    key : controller key와 동일하게 설정 ( file로 설정 )

    value : selectFiles눌러서 선택 (List 타입인경우 여러개 클릭후 선택)

    content-Type : multipart/form-data 입력

     

    Postman Body

     

    file 옆칸에 working directory 경고가 뜬다면 설정 -> Read files outside working directory를 On 해준다.

     

    Postman Body

     

    3.1 Header

    header는 자동으로 body에 형식으로 잡히기 때문에 그대로 진행한다.

    만약에 Content-Type에서 multipart/form-data;로 수정하는경우 boundary 에러가 발생한다.

    boundary를 자동으로 계산할수있게 기본 설정으로 둔다.

     

    Postman Header

    4. Spring-rest-docs

    MockMultiPartFile을 생성한다.

    이미지 데이터

    MockMultipartFile thumbNailImg = new MockMultipartFile("thumbNailImg", "image.png", MediaType.MULTIPART_FORM_DATA_VALUE, "example".getBytes());

     

    Json 데이터

     

    String requestJson = objectMapper.writeValueAsString(
           new AppRoomView(
                 "roomNm",
                 "comName",
                 1,
                 LocalDateTime.now(),
                 LocalDateTime.now(),
                 "roomDesc",
                 "Y",
                 5
           )
    );
    
    MockMultipartFile requestBody = new MockMultipartFile("appRoomView", "", MediaType.APPLICATION_JSON_VALUE, requestJson.getBytes());

     

    mockMvc

    방금 생성한 mock파일을 넣어준다.

     

    mockMvc.perform(multipart("/room/create").file(thumbNailImg).file(requestBody)

     

    

    전체 코드

    @Test
    @DisplayName("객실 정보 수정")
    @WithMockCustomUser
    public void roomModifyTest() throws Exception {
    
        // given
        String requestJson = objectMapper.writeValueAsString(
              new AppRoomView(
                    "roomNm",
                    "comName",
                    1,
                    LocalDateTime.now(),
                    LocalDateTime.now(),
                    "roomDesc",
                    "Y",
                    5
              )
        );
    
        List<MockMultipartFile> roomPicList = new ArrayList<>();
        for (int i = 0; i < 2; i++) {
           roomPicList.add(new MockMultipartFile("roomImgList", i + "image.png"
                 , MediaType.MULTIPART_FORM_DATA_VALUE, "example".getBytes()));
        }
    
        MockMultipartFile thumbNailImg = new MockMultipartFile("thumbNailImg", "image.png", MediaType.MULTIPART_FORM_DATA_VALUE, "example".getBytes());
    
        MockMultipartFile requestBody = new MockMultipartFile("appRoomView", "",
           MediaType.APPLICATION_JSON_VALUE, requestJson.getBytes());
    
        given(appRoomService.companyRoomCreate(
              any(LoginCompanyInfo.class), any(AppRoomView.class), any(MultipartFile.class), any(List.class)))
              .willReturn(
                    AppRoomDTO.builder()
                          .id(1L)
                          .comId(1L)
                          .comName("toryCompany")
                          .soldOutYn("N")
                          .couponYn("Y")
                          .thumbNail("defaultThumbNail")
                          .roomDesc("roomDesc")
                          .maxCount(1)
                          .sellPrc(100000)
                          .roomPicDTOList(
                                List.of(AppRoomPicDTO.builder()
                                      .id(1L)
                                      .picLocation("UUID + pictureImgName")
                                      .picSeq(0)
                                      .modifiedDate(LocalDateTime.now())
                                      .createdDate(LocalDateTime.now())
                                      .build()))
                          .modifiedDate(LocalDateTime.now())
                          .createdDate(LocalDateTime.now())
                          .build()
              );
    
        // when
        mockMvc.perform(multipart("/room/create")
                    .file(roomPicList.get(0))
                    .file(roomPicList.get(1))
                    .file(thumbNailImg)
                    .file(requestBody)
                    .cookie(new Cookie(CommonConstant.ACCOUNT_TOKEN.getName(), "jwtToken"))
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON))
              // then
              .andExpect(status().isOk())
              .andDo(restDocs.document(
                    requestCookies(
                          cookieWithName(CommonConstant.ACCOUNT_TOKEN.getName()).description("JWT 토큰")
                    ),
                    requestParts(
                          partWithName("thumbNailImg").description("객실 썸네일 파일").optional(),
                          partWithName("roomImgList").description("객실 이미지 파일들").optional(),
                          partWithName("appRoomView").description("객실 정보 수정")
                    ),
                    requestPartFields(
                          "appRoomView",
                          fieldWithPath("roomNm").type(JsonFieldType.STRING).description("객실 이름"),
                          fieldWithPath("comName").type(JsonFieldType.STRING).description("회사 이름"),
                          fieldWithPath("sellPrc").type(JsonFieldType.NUMBER).description("객실 가격"),
                          fieldWithPath("checkIn").type(JsonFieldType.STRING).description("체크 인 시간"),
                          fieldWithPath("checkOut").type(JsonFieldType.STRING).description("체크 아웃 시간"),
                          fieldWithPath("roomDesc").type(JsonFieldType.STRING).description("객실 설명"),
                          fieldWithPath("couponYn").type(JsonFieldType.STRING).description("쿠폰 사용 여부"),
                          fieldWithPath("maxCount").type(JsonFieldType.NUMBER).description("총 객실 수")
                    ),
                    responseFields(
                          fieldWithPath("message").type(JsonFieldType.STRING).description("메세지"),
                          fieldWithPath("result").type(JsonFieldType.STRING).description("결과"),
                          fieldWithPath("data").type(JsonFieldType.OBJECT).description("데이터"),
                          fieldWithPath("data.id").type(JsonFieldType.NUMBER).description("객실 ID"),
                          fieldWithPath("data.comId").type(JsonFieldType.NUMBER).description("기업 ID"),
                          fieldWithPath("data.comName").type(JsonFieldType.STRING).description("기업 명"),
                          fieldWithPath("data.soldOutYn").type(JsonFieldType.STRING).description("품절 여부"),
                          fieldWithPath("data.thumbNail").type(JsonFieldType.STRING).description("객실 썸네일 이미지"),
                          fieldWithPath("data.roomDesc").type(JsonFieldType.STRING).description("객실 설명"),
                          fieldWithPath("data.maxCount").type(JsonFieldType.NUMBER).description("총 객실 수"),
                          fieldWithPath("data.roomDesc").type(JsonFieldType.STRING).description("객실 설명"),
                          fieldWithPath("data.sellPrc").type(JsonFieldType.NUMBER).description("객실 가격"),
                          fieldWithPath("data.couponYn").type(JsonFieldType.STRING).description("쿠폰 사용 여부"),
                          fieldWithPath("data.roomPicDTOList").type(JsonFieldType.ARRAY).description("객실 사진 정보"),
                          fieldWithPath("data.roomPicDTOList[].id").type(JsonFieldType.NUMBER).description("객실 사진 ID"),
                          fieldWithPath("data.roomPicDTOList[].picLocation").type(JsonFieldType.STRING).description("객실 사진 위치 명"),
                          fieldWithPath("data.roomPicDTOList[].picSeq").type(JsonFieldType.NUMBER).description("객실 사진 순서"),
                          fieldWithPath("data.roomPicDTOList[].modifiedDate").type(JsonFieldType.STRING).description("객실 사진 생성 일시"),
                          fieldWithPath("data.roomPicDTOList[].createdDate").type(JsonFieldType.STRING).description("객실 사진 수정 일시"),
                          fieldWithPath("data.modifiedDate").type(JsonFieldType.STRING).description("객실 생성 일시"),
                          fieldWithPath("data.createdDate").type(JsonFieldType.STRING).description("객실 수정 일시")
                    ))
              );
    }

     

    완성된 request 필드

     

    댓글

Designed by Tistory.