Spring WebFlux应用单元测试Mock Repository时出现空指针异常
解决PatronService测试中的NullPointerException问题
问题场景
测试PatronService的create()方法时抛出如下异常:
java.lang.NullPointerException: Cannot invoke "reactor.core.publisher.Flux.subscribe()" because "patronFlux" is null at com.budgit.service.PatronService.create(PatronService.java:26)
问题根源分析
- Mockito匹配失败:测试代码中用
Mockito.when(patronRepository.saveAll(patronMono))设置Stub,但create()方法内传入的Mono.just(patron)是新实例,和测试里的patronMono并非同一对象。由于Mono的equals不按内容匹配,导致Stub规则不生效,saveAll()返回null,调用subscribe()时触发NPE。 - 反应式类型错误比较:直接用
assertEquals(expectedResponseStream, responseStream)比较Mono实例是错误的,这会对比对象引用而非实际发布的内容。 - 服务层订阅方式不当:PatronService中直接调用
subscribe()会导致异步执行,无法保证保存操作完成后再返回响应,也不利于测试验证。
解决方案步骤
1. 修正Mockito Stub的匹配规则
使用any(Mono.class)替代具体的Mono实例,确保无论传入哪个Mono对象,Stub规则都能生效:
Mockito.when(patronRepository.saveAll(Mockito.any(Mono.class))).thenReturn(patronFlux);
2. 使用StepVerifier验证反应式结果
反应式类型(Mono/Flux)不能直接用assertEquals比较,需用Reactor的StepVerifier验证发布内容:
StepVerifier.create(responseStream) .expectNext(new Response("Welcome, patron. Can't find the emojis yet -- sorry.")) .verifyComplete();
3. 优化PatronService的反应式调用逻辑
将直接subscribe()改为链式调用,确保保存操作完成后再返回响应,避免异步订阅的不确定性:
public Mono<Response> create(Mono<Patron> patronMono) { return patronRepository.saveAll(patronMono) .then(Mono.just(new Response("Welcome, patron. Can't find the emojis yet -- sorry."))); }
完整修正代码
测试类代码
@WebFluxTest(controllers = {PatronController.class}) @Import({PatronService.class}) @ExtendWith({PatronParameterResolver.class}) class PatronFunctionsSpec { private Patron patron; private Mono<Patron> patronMono; @MockBean private PatronRepository patronRepository; @Autowired private WebTestClient client; @BeforeEach void init(Patron injectedPatron) { patron = injectedPatron; patronMono = Mono.just(patron); } @Nested @DisplayName("Creating a patron.") class CreatingPatron { @Test @DisplayName("PatronService.create() returns success msg in Response obj after creating patron.") void getResponseObjFromServiceCreate() { Flux<Patron> patronFlux = Flux.from(patronMono); // 使用any(Mono.class)匹配任意Mono参数 Mockito.when(patronRepository.saveAll(Mockito.any(Mono.class))).thenReturn(patronFlux); PatronService patronService = new PatronService(patronRepository); Mono<Response> responseStream = patronService.create(Mono.just(patron)); // 使用StepVerifier验证响应内容 StepVerifier.create(responseStream) .expectNextMatches(response -> "Welcome, patron. Can't find the emojis yet -- sorry.".equals(response.getMessage())) .verifyComplete(); } } }
PatronService代码
@Service public class PatronService { private final PatronRepository patronRepository; public PatronService(PatronRepository patronRepository) { this.patronRepository = patronRepository; } public Mono<Response> create(Mono<Patron> patronMono) { // 链式调用,确保保存操作完成后返回响应 return patronRepository.saveAll(patronMono) .then(Mono.just(new Response("Welcome, patron. Can't find the emojis yet -- sorry."))); } }
内容的提问来源于stack exchange,提问作者divad
相关产品推荐
相关产品推荐

