SpringBoot中WebFlux与Mockito结合出现NullPointerException问题排查
修复Spring WebFlux + Mockito单元测试NullPointerException问题
问题根源分析
- 测试类缺少Spring测试上下文注解:
@MockBean需要Spring上下文来初始化mock实例,你的测试类仅标注@AutoConfigureMockMvc,未启动Spring测试上下文,导致productRepository等mock实例未被创建,触发NullPointerException。 - MockMvc错误使用@MockBean:
@AutoConfigureMockMvc会自动配置MockMvc实例,无需手动用@MockBean标注,手动mock会导致MockMvc无法正确绑定到Spring上下文。 - Controller中ExcelService硬编码实例化:
ExcelService excelService = new ExcelService()未通过依赖注入,导致测试类中@MockBean ExcelService无法替换真实实例,破坏测试隔离性。 - Mockito原生初始化与Spring注解冲突:
MockitoAnnotations.openMocks(this)是Mockito原生的mock初始化方式,和Spring Boot的@MockBean注解冲突,导致Spring管理的mock无法正确注入。 - GET请求错误设置请求体:GET请求不需要传递请求体,测试中
content(String.valueOf(flux))属于无效操作,还会导致请求格式错误。
具体修复步骤
1. 修正测试类的Spring注解与依赖注入
给测试类添加@WebFluxTest(ProductController.class)注解启动WebFlux测试上下文,同时正确注入MockMvc:
@WebFluxTest(ProductController.class) @AutoConfigureMockMvc class ProductControllerTest { @Autowired MockMvc mockMvc; @MockBean ProductRepository productRepository; @MockBean ExcelService excelService; // 移除MockitoAnnotations.openMocks(this),由Spring自动管理mock
2. 修改Controller中ExcelService的注入方式
将硬编码实例改为依赖注入,确保测试时能被mock替换:
@CrossOrigin @RestController public class ProductController { @Autowired private ProductRepository productRepository; @Autowired // 改为依赖注入 private ExcelService excelService; // 其他代码不变
3. 修正测试方法中的错误逻辑
移除无效的content设置,同时正确mock ExcelService的返回值,避免调用真实方法:
@Test void should_get_products_in_Excel() throws Exception { ProductData productData = new ProductData(); productData.setTimestamp(1L); productData.setProductId("id"); productData.setProductName("name"); productData.setRegistrationDetails(new ArrayList<>()); productData.setEpaRegNumber(""); productData.setLifecycleState(""); Flux<ProductData> flux = Flux.just(productData); // Mock repository的返回 when(productRepository.findAll()).thenReturn(flux); // Mock ExcelService的返回,避免调用真实方法 CompletableFuture<ByteArrayOutputStream> mockOutputStream = CompletableFuture.completedFuture(new ByteArrayOutputStream()); when(excelService.productsToExcel(any(Flux.class))).thenReturn(mockOutputStream); // 构建GET请求,无需设置content RequestBuilder builder = MockMvcRequestBuilders .get("/products/excel") .accept(MediaType.APPLICATION_OCTET_STREAM); // 执行请求并验证响应 mockMvc.perform(builder) .andExpect(status().isOk()) .andExpect(header().string("Content-Disposition", startsWith("attachment; filename=star-products-"))); }
4. 移除冲突的Mockito初始化代码
删除@BeforeEach中的MockitoAnnotations.openMocks(this),@WebFluxTest和@MockBean已由Spring负责mock的创建和注入,无需手动初始化。
额外优化建议
- 使用
any(Flux.class)等Mockito匹配器时,需导入org.mockito.ArgumentMatchers类。 - 添加响应状态和响应头的验证,确保接口行为符合预期。
- 若需要更轻量的单元测试,可使用Mockito原生注解替代Spring测试注解:
class ProductControllerTest { @Mock ProductRepository productRepository; @Mock ExcelService excelService; @InjectMocks ProductController productController; private MockMvc mockMvc; @BeforeEach void setup() { MockitoAnnotations.openMocks(this); mockMvc = MockMvcBuilders.standaloneSetup(productController).build(); } // 测试方法逻辑不变 }
内容的提问来源于stack exchange,提问作者WorkWork
相关产品推荐
相关产品推荐

