如何验证WebFlux异步调用是否触发指定异常?
测试异步WebClient错误处理器的方案
针对你这种异步无返回值方法的错误处理器测试需求,有几种不用改成block()的可行方案,根据是否允许修改原服务代码来选择:
方案1:给send方法添加可选错误回调(推荐,侵入性低)
如果可以给原方法加一个可选的错误处理参数,就能在测试中捕获异常,同时保持调用端异步特性:
修改后的服务代码
@Component class MyService { val webClient: WebClient by lazy { WebClient.builder() .baseUrl("https://localhost:8080") .build() } // 添加可选的错误消费者参数,默认空实现 fun send(onError: Consumer<Throwable>? = null) { webClient.get() .uri("/posts") .retrieve() .onStatus(HttpStatus::isError) { Mono.error { MyException() } } .bodyToMono(Void::class.java) .subscribe( {}, // 成功回调空实现 { error -> onError?.accept(error) } // 触发传入的错误回调 ) } }
测试代码
用CountDownLatch等待异步操作完成,同时捕获异常:
@Test fun testErrorHandlerTriggersMyException() { mockWebServer.enqueue(MockResponse() .setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.value()) .addHeader("Content-Type", "application/json") ) val latch = CountDownLatch(1) var capturedException: Throwable? = null // 调用send时传入自定义错误回调 myService.send { error -> capturedException = error latch.countDown() // 通知测试线程可以继续 } // 等待异步操作完成,设置5秒超时避免死等 assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue() // 验证捕获的异常类型 assertThat(capturedException).isInstanceOf(MyException::class.java) }
方案2:暴露内部Mono供测试(需修改方法签名)
如果允许让send方法返回内部的Mono,可以用Reactor的StepVerifier来优雅测试:
修改后的服务代码
@Component class MyService { val webClient: WebClient by lazy { WebClient.builder() .baseUrl("https://localhost:8080") .build() } // 返回内部的Mono,调用端仍可选择异步subscribe fun send(): Mono<Void> { val requestMono = webClient.get() .uri("/posts") .retrieve() .onStatus(HttpStatus::isError) { Mono.error { MyException() } } .bodyToMono(Void::class.java) requestMono.subscribe() // 保持原异步逻辑 return requestMono } }
测试代码
@Test fun testErrorHandlerTriggersMyException() { mockWebServer.enqueue(MockResponse() .setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.value()) .addHeader("Content-Type", "application/json") ) val requestMono = myService.send() // 用StepVerifier验证Mono是否抛出指定异常 StepVerifier.create(requestMono) .expectError(MyException::class.java) .verify() }
方案3:全局Hook捕获异常(无需修改服务代码)
如果完全不能修改原服务代码,可以通过Reactor的全局错误Hook来捕获异常,但要注意测试后清理Hook避免影响其他用例:
测试代码
@Test fun testErrorHandlerTriggersMyException() { mockWebServer.enqueue(MockResponse() .setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.value()) .addHeader("Content-Type", "application/json") ) val latch = CountDownLatch(1) var capturedException: Throwable? = null // 注册全局错误Hook,捕获异常 val originalHook = Hooks.onOperatorError { _, error -> capturedException = error latch.countDown() error // 抛出原异常,不影响原有逻辑 } try { myService.send() // 等待异步操作完成 assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue() // 验证异常类型 assertThat(capturedException).isInstanceOf(MyException::class.java) } finally { // 必须恢复原Hook,避免污染其他测试 Hooks.resetOnOperatorError() } }
内容的提问来源于stack exchange,提问作者J.Kennsy
相关产品推荐
相关产品推荐

