ABOUT ME

  • [스프링부트] 이미지 업로드 테스트 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.