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

SpringBoot JUnit测试@RequestPart嵌套列表参数缺失报错求助

问题:MockMvc测试含@RequestPart的PUT接口时抛出MissingServletRequestPartException

原Controller代码

@PutMapping("/update/{id}")
public void update(
        @PathVariable(name = "id") Long id,
        @RequestPart List<List<Long>> affectedIds,
        @RequestPart String compatibility,
        @RequestPart(name = "procId") String procId,
        @RequestHeader(value = "timeZone") String timeZone) {

    TimeZone userTimeZone = TimeZone.getTimeZone(timeZone);
    myService.update(
            id, affectedIds, compatibility, Long.parseLong(procId), userTimeZone);
}

原测试代码及错误

测试代码

@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class MyControllerTest {

    @InjectMocks
    private MyController myController;

    @Mock
    private MyService myService;

    @Autowired(required=true)
    private MockMvc mockMvc;

    @Test
    void update() throws Exception {

        Long id = 123L;
        List<List<Long>> affectedIds = List.of(
                List.of(1L, 2L),
                List.of(3L, 4L)
        );
        String compatibility = "oncecompatible";
        String procId = "11";
        String timeZone = "UTC";

        TimeZone userTimeZone = TimeZone.getTimeZone(timeZone);

        doNothing().when(myService).update(id, affectedIds, compatibility,
                Long.parseLong(procId), userTimeZone);

        ObjectMapper mapper = createObjectMapper();
        String s = mapper.writeValueAsString(affectedIds);

        ResultActions response = mockMvc.perform(MockMvcRequestBuilders.put
                ("/api/proc/check/update/{id}", id)
                .content(s)
                .content(compatibility)
                .content(procId)
                .header("timeZone", timeZone)
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8));

        response.andExpect(status().isOk());

    }

    private static ObjectMapper createObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        mapper.registerModule(new JavaTimeModule());
        return mapper;
    }

}

错误信息

org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'affectedIds' is not present.

问题分析

  1. 请求内容覆盖:多次调用.content()会覆盖之前设置的内容,最终只有最后一次传入的procId作为请求体,导致affectedIds和compatibility两个请求part缺失。
  2. 请求类型不匹配:@RequestPart对应multipart/form-data类型请求,原测试设置的contentType为application/json,不符合接口要求。
  3. Mock注入冲突:同时使用@SpringBootTest(启动上下文)和@InjectMocks+@Mock,MockMvc实际使用容器中的Controller实例,而非@InjectMocks创建的实例,导致Mock的MyService未被正确注入。

修正后的测试代码

@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class MyControllerTest {

    @MockBean
    private MyService myService;

    @Autowired
    private MockMvc mockMvc;

    @Test
    void update() throws Exception {
        Long id = 123L;
        List<List<Long>> affectedIds = List.of(
                List.of(1L, 2L),
                List.of(3L, 4L)
        );
        String compatibility = "oncecompatible";
        String procId = "11";
        String timeZone = "UTC";
        TimeZone userTimeZone = TimeZone.getTimeZone(timeZone);

        // 配置MockService的行为
        doNothing().when(myService).update(id, affectedIds, compatibility,
                Long.parseLong(procId), userTimeZone);

        ObjectMapper mapper = createObjectMapper();
        String affectedIdsJson = mapper.writeValueAsString(affectedIds);

        // 构建multipart类型的PUT请求
        ResultActions response = mockMvc.perform(MockMvcRequestBuilders.multipart(HttpMethod.PUT, "/api/proc/check/update/{id}", id)
                // 添加affectedIds请求part,指定JSON类型
                .part(new MockMultipartFile("affectedIds", "affectedIds.json", "application/json", affectedIdsJson.getBytes(StandardCharsets.UTF_8)))
                // 添加compatibility请求part
                .part("compatibility", compatibility)
                // 添加procId请求part
                .part("procId", procId)
                // 设置请求头timeZone
                .header("timeZone", timeZone)
                .accept(MediaType.APPLICATION_JSON));

        // 验证响应状态
        response.andExpect(status().isOk());
        // 验证Service方法是否被正确调用
        verify(myService, times(1)).update(id, affectedIds, compatibility, Long.parseLong(procId), userTimeZone);
    }

    private static ObjectMapper createObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        mapper.registerModule(new JavaTimeModule());
        return mapper;
    }
}

关键修改点

  • 改用MockBean注入:用@MockBean替代@Mock+@InjectMocks,确保容器中的Controller能正确使用Mock的MyService。
  • 构建Multipart请求:使用MockMvcRequestBuilders.multipart()并指定HttpMethod.PUT,因为默认multipart方法为POST,需显式指定PUT。
  • 逐个添加RequestPart:用.part()方法为每个@RequestPart参数指定名称和内容,复杂对象需序列化为JSON字符串并设置对应contentType。
  • 补充验证逻辑:增加verify()验证Service方法是否被正确调用,确保测试完整性。

内容的提问来源于stack exchange,提问作者shikhar saxena

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 10:35:57