쇼핑몰 토이 프로젝트 중 해당 품목의 이미지를 첨부하고 품목 정보를 입력해 아이템을 등록하는 기능을 구현하려고 MultipartFile은 @RequestParam으로 Dto는 @RequestBody를 사용해서 받을려고 했지만 아래의 오류가 발생했다.
2023-06-28T21:17:30.208+09:00 WARN 7120 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'multipart/form-data;boundary=--------------------------150556874451848119877768;charset=UTF-8' is not supported]
오류를 해결하고자 구글링을 하였고 그 결과 MultipartFile과 Dto를 함께 요청 받으려면 두개 모두 @RequestPart를 이용해야 한다.
❗ 해당 방법을 사용하면 Swaager로 테스트가 불가능하여 Postman을 사용하였습니다.
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/items")
public class ItemController {
private final ItemService itemService;
@Tag(name = "Item", description = "품목 API")
@Operation(summary = "품목 추가")
@PostMapping
public Response<ItemDto> createItem(@RequestPart ItemCreateRequest request, @RequestPart MultipartFile multipartFile) {
ItemDto response = itemService.saveItem(request, multipartFile);
return Response.success(response);
}
}
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
private final BrandRepository brandRepository;
private final AwsS3Service awsS3Service;
public ItemDto saveItem(ItemCreateRequest request, MultipartFile multipartFile) {
itemRepository.findItemByItemName(request.getItemName())
.ifPresent((item -> {
throw new AppException(DUPLICATE_ITEM, DUPLICATE_ITEM.getMessage());
}));
Brand findBrand = getBrand(request.getBrandName());
String originImageUrl = awsS3Service.uploadBrandOriginImage(multipartFile);
request.setItemPhotoUrl(originImageUrl);
Item savedItem = itemRepository.save(request.toEntity(findBrand));
return savedItem.toItemDto();
}
// 브랜드 이름을 통해 해당 브랜드를 찾는 메소드
private Brand getBrand(String brandName) {
Brand findBrand = brandRepository.findBrandByName(brandName)
.orElseThrow(() -> new AppException(BRAND_NOT_FOUND, BRAND_NOT_FOUND.getMessage()));
return findBrand;
}
}
@WebMvcTest(ItemController.class)
class ItemControllerTest {
@MockBean
ItemService itemService;
@Autowired
ObjectMapper objectMapper;
@Autowired
private MockMvc mockMvc;
@Test
@DisplayName("브랜드 등록 성공")
@WithMockCustomUser(role = CustomerRole.ROLE_ADMIN)
public void create_brand_success() throws Exception {
// given
ItemCreateRequest request = ItemCreateRequest.builder()
.itemName("testItem")
.price(21000)
.stock(1000)
.brandName("testBrand")
.itemPhotoUrl("test")
.build();
String valueAsString = objectMapper.writeValueAsString(request);
final String fileName = "testImage1";
final String contentType = "png";
MockMultipartFile multipartFile = setMockMultipartFile(fileName, contentType);
Brand findBrand = Brand.builder()
.id(1L)
.name("testBrand")
.originImagePath("s3/brand/url")
.build();
findBrand.setCreatedDate(LocalDateTime.now());
findBrand.setLastModifiedDate(LocalDateTime.now());
ItemDto response = ItemDto.builder()
.itemName("testItem")
.stock(1000)
.price(21000)
.itemPhotoUrl("s3/item/url")
.brand(findBrand)
.build();
given(itemService.saveItem(any(ItemCreateRequest.class), any(MockMultipartFile.class)))
.willReturn(response);
// when & then
mockMvc.perform(multipart("/api/v1/items")
.file(new MockMultipartFile("request", "", "application/json", valueAsString.getBytes(StandardCharsets.UTF_8)))
.file(multipartFile)
.contentType(MULTIPART_FORM_DATA)
.accept(APPLICATION_JSON)
.characterEncoding("UTF-8")
.with(csrf()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.resultCode").value("SUCCESS"))
.andExpect(jsonPath("$.result.itemName").value(response.getItemName()))
.andExpect(jsonPath("$.result.price").value(response.getPrice()))
.andExpect(jsonPath("$.result.stock").value(response.getStock()))
.andExpect(jsonPath("$.result.itemPhotoUrl").value(response.getItemPhotoUrl()))
.andExpect(jsonPath("$.result.brand.createdDate").exists())
.andExpect(jsonPath("$.result.brand.deletedDate").doesNotExist())
.andExpect(jsonPath("$.result.brand.lastModifiedDate").exists())
.andExpect(jsonPath("$.result.brand.id").value(response.getBrand().getId()))
.andExpect(jsonPath("$.result.brand.name").value(response.getBrand().getName()))
.andExpect(jsonPath("$.result.brand.originImagePath").value(response.getBrand().getOriginImagePath()))
.andDo(print());
}
private MockMultipartFile setMockMultipartFile(String fileName, String contentType) {
return new MockMultipartFile("multipartFile", fileName + "." + contentType, contentType, "<<data>>".getBytes());
}
}
Body가 form-data인지 dto값은 JSON 형식인지 등 세부사항들도 정확히 입력해야 한다.
같은 문제에 직면했는데 덕분에 해결했습니다 감사합니다~