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

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)

问题根源分析

  1. Mockito匹配失败:测试代码中用Mockito.when(patronRepository.saveAll(patronMono))设置Stub,但create()方法内传入的Mono.just(patron)是新实例,和测试里的patronMono并非同一对象。由于Mono的equals不按内容匹配,导致Stub规则不生效,saveAll()返回null,调用subscribe()时触发NPE。
  2. 反应式类型错误比较:直接用assertEquals(expectedResponseStream, responseStream)比较Mono实例是错误的,这会对比对象引用而非实际发布的内容。
  3. 服务层订阅方式不当: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 21:25:23