Spring Boot中Mono<Void>单元测试抛出ClassCastException问题排查
我来帮你分析这个问题,先看核心错误和代码里的问题点:
错误日志
MesavisadoCalculadoraAgentTest > mesavisadoCalculadoraNotificaTest() FAILED
java.lang.ClassCastException at MesavisadoCalculadoraAgentTest.java:65java.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类型强制转换。修复业务方法的返回逻辑后,测试就能正常执行了。




