Spring Boot中含文件上传的菜谱控制器测试方案
解决菜谱控制器图片上传测试问题
你的测试无法正常运行的核心问题是错误地使用param()传递MultipartFile[]参数。Spring MVC Test中处理文件上传需要用file()方法,而非普通参数。下面是覆盖无图片、单图片、多图片三种场景的完整测试套件:
测试类基础配置
首先确保测试类正确配置,模拟控制器依赖的服务,避免依赖外部资源:
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.mock.web.MockMultipartFile; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.springframework.http.MediaType; @WebMvcTest(RecipeController.class) // 替换为你的控制器实际类名 public class RecipeControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; // 模拟用户服务 @MockBean private ImageService imageService; // 模拟图片存储服务 @MockBean private RecipeService recipeService; // 模拟菜谱服务
场景1:无图片上传
无需添加任何文件参数,控制器会使用表单默认的空数组:
@Test @WithMockUser @DisplayName("创建菜谱 - 无图片上传") public void createRecipeWithoutImages() throws Exception { mockMvc.perform(multipart("/recipe/createRecipe") .contentType(MediaType.MULTIPART_FORM_DATA) // 基本字段 .param("title", "无图测试菜谱") .param("description", "这是一个没有图片的测试菜谱") .param("servings", "2") .param("recipeOwner", "test@example.com") // 食材字段 .param("ingredients[0].name", "鸡蛋") .param("ingredients[0].amount", "2") .param("ingredients[0].unit", "个") // 步骤字段 .param("instructions[0].instruction", "打散鸡蛋") // 标签字段 .param("tags[0].name", "早餐") ) .andExpect(status().isOk()) .andExpect(content().string(containsString("Recipe Created!"))); // 验证图片服务被调用,传入空数组 verify(imageService).saveImages(new MultipartFile[0]); // 验证菜谱服务被调用 verify(recipeService).save(any(RecipeCreationEvent.class)); }
场景2:单图片上传
创建单个MockMultipartFile并通过file()方法传入:
@Test @WithMockUser @DisplayName("创建菜谱 - 单图片上传") public void createRecipeWithSingleImage() throws Exception { // 模拟一张图片文件 MockMultipartFile mockImage = new MockMultipartFile( "imageRoutes", "test-image.jpg", "image/jpeg", "test image content".getBytes() ); mockMvc.perform(multipart("/recipe/createRecipe") .contentType(MediaType.MULTIPART_FORM_DATA) // 基本字段 .param("title", "单图测试菜谱") .param("description", "这是一个单图片的测试菜谱") .param("servings", "2") .param("recipeOwner", "test@example.com") // 食材、步骤、标签字段 .param("ingredients[0].name", "鸡蛋") .param("ingredients[0].amount", "2") .param("ingredients[0].unit", "个") .param("instructions[0].instruction", "打散鸡蛋") .param("tags[0].name", "早餐") // 添加单张图片 .file(mockImage) ) .andExpect(status().isOk()) .andExpect(content().string(containsString("Recipe Created!"))); // 验证图片服务被调用,传入包含单张图片的数组 verify(imageService).saveImages(any(MultipartFile[].class)); verify(recipeService).save(any(RecipeCreationEvent.class)); }
场景3:多图片上传
创建多个MockMultipartFile,多次调用file()方法传入(同名参数会自动绑定到数组):
@Test @WithMockUser @DisplayName("创建菜谱 - 多图片上传") public void createRecipeWithMultipleImages() throws Exception { // 模拟多张图片文件 MockMultipartFile image1 = new MockMultipartFile( "imageRoutes", "image1.jpg", "image/jpeg", "image 1 content".getBytes() ); MockMultipartFile image2 = new MockMultipartFile( "imageRoutes", "image2.png", "image/png", "image 2 content".getBytes() ); mockMvc.perform(multipart("/recipe/createRecipe") .contentType(MediaType.MULTIPART_FORM_DATA) // 基本字段 .param("title", "多图测试菜谱") .param("description", "这是一个多图片的测试菜谱") .param("servings", "4") .param("recipeOwner", "test@example.com") // 食材、步骤、标签字段 .param("ingredients[0].name", "面粉") .param("ingredients[0].amount", "200") .param("ingredients[0].unit", "克") .param("instructions[0].instruction", "混合面粉和水") .param("tags[0].name", "烘焙") // 添加多张图片 .file(image1) .file(image2) ) .andExpect(status().isOk()) .andExpect(content().string(containsString("Recipe Created!"))); // 验证图片服务被调用,传入包含多张图片的数组 verify(imageService).saveImages(any(MultipartFile[].class)); verify(recipeService).save(any(RecipeCreationEvent.class)); } }
关键注意事项
- 文件参数绑定:
MultipartFile[]类型的参数需要通过file()方法传递,而非param()。多个同名文件会自动绑定到数组中。 - Mock依赖服务:使用
@MockBean模拟imageService、recipeService等,避免测试过程中实际操作文件系统或数据库。 - 验证业务逻辑:通过
verify()方法验证服务方法是否被正确调用,确保控制器的业务逻辑符合预期。 - 表单字段格式:嵌套对象(如
IngredientRow)的参数格式ingredients[0].name是正确的,Spring会自动绑定到列表中。
内容的提问来源于stack exchange,提问作者namer
相关产品推荐
相关产品推荐

