You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 09:15:01