使用MockMvc测试Multipart文件上传接口时遇空指针异常求助
解决MockMvc测试空指针异常问题
问题场景
编写JUnit测试用MockMvc触发REST请求时,出现空指针异常:
java.lang.NullPointerException: Cannot invoke "org.springframework.test.web.servlet.ResultActions.andExpect(org.springframework.test.web.servlet.ResultMatcher)" because the return value of "org.springframework.test.web.servlet.MockMvc.perform(org.springframework.test.web.servlet.RequestBuilder)" is null
对应的控制器方法:
@RequestMapping(value = "data/upload", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA) @CrossOrigin @Loggable (activityname = ActivityLogConstants.CONFIG_DATA_UPLOAD, operationName =ActivityLogConstants.UPLOAD_OPERATIONNAME) public Response importConfigData(HttpServletRequest request, @RequestParam("file") MultipartFile inFile, @RequestParam("rowData") String rowData) throws ServiceException { // 业务逻辑 }
原测试代码:
@ExtendWith(SpringExtension.class) public class ConfigUploadTest { @MockBean private MockMvc mockMvc; @InjectMocks private UploadController uploadController; @Test public void vnfmService_ConfigUploadShouldReturnStatusOkTest() throws Exception { MockMultipartFile file = new MockMultipartFile( "file", // The request parameter name "test.xml", // The original filename (with .xml extension) "application/xml", // The content type for XML "<?xml version=\"1.0\" encoding=\"UTF-8\"?><data>This is some XML content</data>".getBytes()); MockMultipartHttpServletRequestBuilder request = MockMvcRequestBuilders.multipart("/data/upload"); request.file("file", file.getBytes()); request.param("rowData","{\"test\":\"test\"}"); mockMvc.perform(request).andExpect(status().isOk()); } }
需求:不加载完整应用上下文,仅测试单个控制器。
错误原因
- MockMvc初始化错误:用
@MockBean标注MockMvc是错误的,@MockBean用于模拟Spring容器中的依赖Bean,但MockMvc是测试工具,需要手动构建而非模拟,导致mockMvc实例为null,调用perform时返回null触发NPE。 - Multipart请求构建不当:
request.file("file", file.getBytes())的用法有误,应该直接传入MockMultipartFile对象而非字节数组。
修复方案
- 移除
@MockBean注解的MockMvc字段,改为在测试类中手动初始化MockMvc,使用MockMvcBuilders.standaloneSetup()针对单个控制器构建,无需加载完整上下文。 - 调整Multipart请求的构建方式,直接将
MockMultipartFile对象添加到请求中。 - 确保控制器的依赖(如果有的话)通过
@InjectMocks或手动注入完成。
完整修复后的测试代码
@ExtendWith(SpringExtension.class) public class ConfigUploadTest { private MockMvc mockMvc; @InjectMocks private UploadController uploadController; @BeforeEach void setUp() { // 针对单个控制器构建MockMvc,无需加载完整上下文 mockMvc = MockMvcBuilders.standaloneSetup(uploadController) // 若控制器有@CrossOrigin注解,需添加对应处理器 .addFilter(new CorsFilter(new UrlBasedCorsConfigurationSource())) .build(); } @Test public void vnfmService_ConfigUploadShouldReturnStatusOkTest() throws Exception { MockMultipartFile file = new MockMultipartFile( "file", "test.xml", "application/xml", "<?xml version=\"1.0\" encoding=\"UTF-8\"?><data>This is some XML content</data>".getBytes()); mockMvc.perform(MockMvcRequestBuilders.multipart("/data/upload") .file(file) // 直接传入MockMultipartFile对象 .param("rowData", "{\"test\":\"test\"}")) .andExpect(status().isOk()); } }
额外说明
- 如果控制器依赖了其他服务(如Service层),需用
@Mock标注这些依赖,再通过@InjectMocks注入到控制器中,避免测试时出现依赖缺失问题。 standaloneSetup方式适合快速测试单个控制器,无需启动整个Spring上下文,符合你的需求。
内容的提问来源于stack exchange,提问作者manjosh
相关产品推荐
相关产品推荐

