You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Spring Boot中Mono<Void>单元测试抛出ClassCastException问题排查

Spring Boot中Mono单元测试抛出ClassCastException问题排查

我来帮你分析这个问题,先看核心错误和代码里的问题点:

错误日志

MesavisadoCalculadoraAgentTest > mesavisadoCalculadoraNotificaTest() FAILED
java.lang.ClassCastException at MesavisadoCalculadoraAgentTest.java:65

java.lang.ClassCastException: reactor.core.publisher.MonoIgnorePublisher cannot be cast to java.lang.Void
at infrastructure.agent.apimesavisadocalculadora.MesaVisadoCalculadoraAgent.lambda$mesaVisadoCalculadoraNotificar$0(MesaVisadoCalculadoraAgent.java:45)
at reactor.core.publisher.MonoCallable.block(MonoCallable.java:81)
at reactor.core.publisher.MonoCallable.block(MonoCallable.java:74)
at infrastructure.agent.apimesavisadocalculadora.MesavisadoCalculadoraAgentTest.mesavisadoCalculadoraNotificaTest(MesavisadoCalculadoraAgentTest.java:65)


问题根源分析

先看你的业务方法代码:

public Mono<Void> mesaVisadoCalculadoraNotificar(NotificarIn in) {
    setTimeoutErrorCode("MVC-01");
    return Mono.fromCallable(() -> {
        WebTarget path = target.path(MesaVisadoCalculadora.NOTIFICAR.getUri());
        
        return restHelper.performRequest(path, in, new GenericType<Void>(){}, HttpAction.POST);
    });
}

这里的核心错误是:Mono.fromCallable要求内部的Callable返回Void类型,但你却返回了一个Mono<Void>对象

Mono.fromCallable的作用是把一个返回T的同步方法包装成Mono<T>,但你在lambda里返回的是restHelper.performRequest的结果——也就是一个Mono<Void>实例,而不是Void类型的值。当你调用block()时,Reactor会尝试把这个Mono对象强制转换成Void,这就触发了ClassCastException


修复步骤

1. 修复业务方法(最关键)

因为restHelper.performRequest已经返回了Mono<Void>,你完全不需要用Mono.fromCallable包装它,直接返回这个结果即可:

public Mono<Void> mesaVisadoCalculadoraNotificar(NotificarIn in) {
    setTimeoutErrorCode("MVC-01");
    WebTarget path = target.path(MesaVisadoCalculadora.NOTIFICAR.getUri());
    return restHelper.performRequest(path, in, new GenericType<Void>(){}, HttpAction.POST);
}

如果setTimeoutErrorCode是可能抛出异常的同步方法,可以用Mono.defer包装确保异常被Reactor捕获:

public Mono<Void> mesaVisadoCalculadoraNotificar(NotificarIn in) {
    return Mono.defer(() -> {
        setTimeoutErrorCode("MVC-01");
        WebTarget path = target.path(MesaVisadoCalculadora.NOTIFICAR.getUri());
        return restHelper.performRequest(path, in, new GenericType<Void>(){}, HttpAction.POST);
    });
}

2. 优化单元测试

你的测试还有两个不合理的地方:

  • 断言Assertions.assertNotNull(promise);没有意义,因为promise是你刚创建的对象,必然不为空
  • 没有验证依赖的交互是否符合预期

优化后的测试代码:

@Test
public void mesavisadoCalculadoraNotificaTest(){
    // 给定
    when(target.path(Mockito.anyString())).thenReturn(target);
    when(restHelper.performRequest(
        Mockito.any(WebTarget.class), 
        Mockito.eq(in), // 用eq匹配具体入参,更严谨
        Mockito.any(GenericType.class),
        Mockito.eq(HttpAction.POST)
    )).thenReturn(Mono.empty());

    // 执行
    Mono<Void> result = agent.mesaVisadoCalculadoraNotificar(in);
    result.block(); // 现在不会再抛ClassCastException

    // 验证交互是否符合预期
    Mockito.verify(target).path(MesaVisadoCalculadora.NOTIFICAR.getUri());
    Mockito.verify(restHelper).performRequest(
        Mockito.any(WebTarget.class), 
        Mockito.eq(in), 
        Mockito.any(GenericType.class),
        Mockito.eq(HttpAction.POST)
    );
}

总结

之前的ClassCastException完全是因为业务方法错误地用Mono.fromCallable包装了一个已经返回Mono的异步方法,导致Reactor把Mono实例当成Void类型强制转换。修复业务方法的返回逻辑后,测试就能正常执行了。

火山引擎 最新活动