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.
问题分析
- 请求内容覆盖:多次调用
.content()会覆盖之前设置的内容,最终只有最后一次传入的procId作为请求体,导致affectedIds和compatibility两个请求part缺失。 - 请求类型不匹配:
@RequestPart对应multipart/form-data类型请求,原测试设置的contentType为application/json,不符合接口要求。 - 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
相关产品推荐
相关产品推荐

