如何用MockMvc测试带MultipartFile的POST API?400错误排查
问题排查与正确测试示例
我需要测试一个接收@Valid实体对象、BindingResult和MultipartFile参数的POST接口,根据Spring官方文档,测试时需使用MockMvc的multipart方法而非post,但实际测试时响应状态码为400,不符合预期的200。以下是我的测试代码、接口代码,请求排查问题并给出正确测试示例。
原测试类及方法
@Autowired private MockMvc mockMvc; @MockBean private ProductService productService; @Test void newProduct() { // Given Product product = new Product("Test", "Diary", "Lorem 24", "fresh-pepper.jpg"); product.setId(1L); product.setPath("dummy photo path"); MockMultipartFile multipartFile = new MockMultipartFile("file", "fresh-pepper.jpg", "image/png, image/jpg, image/jpeg", "random image".getBytes()); //When when(productService.saveProduct(product, multipartFile)).thenReturn(product); //Then try { this.mockMvc.perform(multipart("/dummy/api/v1/addNew") .file(multipartFile)) .andDo(print()) .andExpect(status().isOk()); } catch (Exception e) { e.printStackTrace(); } }
原Rest接口方法(类上标注@RequestMapping("/dummy/api/v1"))
@PostMapping(path = "/addNew", consumes = {MULTIPART_FORM_DATA_VALUE}) public ResponseEntity<Product> newProduct(@Valid Product product, BindingResult bindingResult, @RequestParam("file") MultipartFile file) { if (bindingResult.hasFieldErrors()){ bindingResult.getFieldErrors().forEach(fieldError -> LOGGER.error("Error " + fieldError.getField())); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } return new ResponseEntity<>(productService.saveProduct(product, file), HttpStatus.OK); }
问题排查
- 未传递Product实体参数:测试代码仅提交了MultipartFile,未传递Product的属性字段,导致
@Valid校验失败,BindingResult检测到字段错误,接口返回400。 - MockMultipartFile的ContentType格式错误:构造时传入多个用逗号分隔的MIME类型,不符合规范,应传入单个合法MIME类型(如
image/jpeg)。 - Mock参数匹配过严:
when(productService.saveProduct(product, multipartFile))依赖对象实例匹配,但接口中接收的Product是从表单参数构建的新对象,与测试代码中的实例并非同一个,会导致Mock不生效。
正确测试示例
修改后的测试代码如下:
@Autowired private MockMvc mockMvc; @MockBean private ProductService productService; @Test void newProduct() throws Exception { // Given String productName = "Test"; String category = "Diary"; String description = "Lorem 24"; String imageName = "fresh-pepper.jpg"; Long productId = 1L; String path = "dummy photo path"; // 构造合法的MultipartFile MockMultipartFile multipartFile = new MockMultipartFile("file", imageName, "image/jpeg", "random image".getBytes()); // 构造预期返回的Product对象 Product expectedProduct = new Product(productName, category, description, imageName); expectedProduct.setId(productId); expectedProduct.setPath(path); // 使用参数匹配器避免实例严格匹配问题 when(productService.saveProduct(any(Product.class), eq(multipartFile))) .thenReturn(expectedProduct); // 发起multipart请求,添加Product属性作为表单参数 this.mockMvc.perform(multipart("/dummy/api/v1/addNew") .file(multipartFile) .param("name", productName) .param("category", category) .param("description", description) .param("imageName", imageName) .param("id", productId.toString()) .param("path", path)) .andDo(print()) .andExpect(status().isOk()) // 校验返回的Product字段正确性 .andExpect(jsonPath("$.id").value(productId)) .andExpect(jsonPath("$.name").value(productName)); }
修改说明
- 添加Product表单参数:通过
param()方法将Product的每个属性以表单字段形式传递,确保接口能正确实例化对象并通过@Valid校验。 - 修正MultipartFile的ContentType:改为单个合法的MIME类型,符合规范要求。
- 优化Mock参数匹配:使用
any(Product.class)和eq(multipartFile)作为匹配器,避免实例不匹配导致的Mock失效。 - 移除冗余try-catch:直接抛出Exception,让JUnit自动处理,符合测试规范。
- 新增返回结果校验:通过
jsonPath验证返回的Product字段,确保接口返回数据符合预期。
内容的提问来源于stack exchange,提问作者iTchTheRightSpot
相关产品推荐
相关产品推荐

