
本文详解如何通过 Mockito 模拟异常流、配合 StepVerifier 或 blockOptional() 验证 Mono.onErrorResume 的各类错误分支,避免因直接抛出异常导致流中断,并提供更健壮的错误分类策略。
本文详解如何通过 mockito 模拟异常流、配合 stepverifier 或 `blockoptional()` 验证 `mono.onerrorresume` 的各类错误分支,避免因直接抛出异常导致流中断,并提供更健壮的错误分类策略。
在响应式编程中,onErrorResume 是核心的错误恢复操作符,但其行为高度依赖于错误是否真实进入 Reactive 流(即被封装为 Mono.error()),而非在链外直接 throw。你当前的单元测试失败的根本原因在于:doThrow(...) 会令 myWebClient.getResponse() 同步抛出异常,导致 responseDataMono 根本无法被创建(返回 null),整个 Mono 流从未启动,自然无法触发 onErrorResume。
✅ 正确做法是:让 mock 方法返回一个 Mono.error(...),使错误作为数据流的一部分被下游操作符感知:
// ✅ 正确:返回一个“错误型”Mono,流可继续执行 onErrorResume
doReturn(Mono.error(new WebClientResponseException(400, "Bad Request", null, null, null)))
.when(myWebClient)
.getResponse(anyString());
⚠️ 注意:Mono.error(() -> ...) 是惰性求值写法(推荐用于复杂构造),但对简单异常,直接 Mono.error(new Exception()) 更清晰且无副作用。
推荐测试方案:使用 StepVerifier(最佳实践)
StepVerifier 是 Project Reactor 官方推荐的响应式流断言工具,能精确验证事件序列(包括 error、empty、next)、背压行为和时序,比 blockOptional() 更可靠、更符合响应式理念:
@Test
void testOnErrorResumeForBadRequest() {
// Arrange
MyWebClient mockClient = mock(MyWebClient.class);
doReturn(Mono.error(new WebClientResponseException(400, "Bad Request", null, null, null)))
.when(mockClient)
.getResponse(anyString());
MyService service = new MyService(mockClient); // 使用真实构造(非 @InjectMocks)
// Act & Assert
StepVerifier.create(service.myServiceMethod())
.verifyComplete(); // 因 onErrorResume 返回 Mono.empty(),最终流正常完成(非错误终止)
}
若需验证 onErrorResume 中的特定逻辑(如日志、外部调用),可结合 doOnNext/doOnError + AtomicBoolean 或 ArgumentCaptor 进行副作用断言;但核心流行为应由 StepVerifier 驱动。
替代方案:blockOptional()(适用于快速验证)
对于简单场景,也可用阻塞方式验证结果:
@Test
void testOnErrorResumeWithBlock() {
MyWebClient mockClient = mock(MyWebClient.class);
doReturn(Mono.error(new WebClientResponseException(404, "Not Found", null, null, null)))
.when(mockClient)
.getResponse(anyString());
Mono<student> result = mockClient.getResponse("url")
.onErrorResume(ex -> {
if (ex instanceof WebClientResponseException.NotFound) {
return Mono.empty(); // 或返回默认 Student
}
return Mono.empty();
});
Optional<student> actual = result.blockOptional();
assertThat(actual).isEmpty(); // 断言返回空值
}</student></student>
❗警告:blockOptional() 仅限测试使用,生产代码中严禁阻塞,否则破坏响应式非阻塞特性。
进阶建议:重构 onErrorResume 提升可测性与健壮性
原代码依赖 ex.getMessage().contains("400") 判断状态码,存在严重缺陷:
- 消息内容不稳定(如国际化、堆栈混入);
- 400 与 404 错误消息可能重叠(如 "400 Bad Request: user not found");
- 无法区分 WebClientResponseException 与其他超时异常(如 TimeoutException)。
✅ 推荐按异常类型精准匹配(类型安全、语义清晰):
return responseDataMono
.onErrorResume(WebClientResponseException.NotFound.class, err -> {
log.warn("Student not found, returning empty", err);
return Mono.empty();
})
.onErrorResume(WebClientResponseException.BadRequest.class, err -> {
log.error("Invalid request for student lookup", err);
return Mono.empty();
})
.onErrorResume(TimeoutException.class, err -> {
log.warn("Request timeout, fallback to cached data", err);
return getFallbackStudent(); // 自定义 fallback Mono
})
.onErrorResume(err -> {
log.error("Unexpected error in student fetch", err);
return Mono.error(err); // 或统一降级
});
此写法不仅逻辑清晰、易于测试(只需 mock 对应子类异常),也完全规避了字符串解析风险,是响应式错误处理的最佳实践。
总结:测试 onErrorResume 的关键是确保错误以 Mono.error() 形式进入流,并优先选用 StepVerifier 进行声明式验证;同时,应摒弃基于异常消息的脆弱判断,转向基于异常类型的强类型恢复策略。











