Polly重试策略无法捕获NSubstitute模拟抛出的HttpRequestException异常
问题分析
你的Polly重试策略在_tokenInfrastructure.GetTokenAsync()抛出HttpRequestException时未触发重试,但在_timezoneInfrastructure.ProcessRequest()返回非空字符串时正常工作,说明策略的重试逻辑本身是有效的,问题出在异常捕获环节。
可能的原因及解决方案
1. 确认策略类型匹配
确保_retryPolicy被明确声明为AsyncPolicy<string>类型,因为它处理的是返回string的异步操作:
// 明确声明泛型策略类型 AsyncPolicy<string> _retryPolicy; // 初始化策略 var delay = Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromMilliseconds(RetryDelay), RetryCount); _retryPolicy = Policy .Handle<HttpRequestException>() .Or<CustomException>() .OrResult<string>(response => !string.IsNullOrEmpty(response)) .WaitAndRetryAsync(delay);
若策略被错误声明为非泛型的AsyncPolicy,可能导致异步异常捕获失效。
2. 修正异常模拟方式
在测试中使用NSubstitute专门的异步异常模拟方法ThrowsAsync,替代手动创建Task.FromException,避免潜在的模拟逻辑问题:
// 替换原有的异常模拟代码 _tokenInfrastructure.GetTokenAsync().ThrowsAsync(new HttpRequestException());
3. 排查隐藏的异常捕获
检查RetryProcessRequest方法,确保没有try-catch块吃掉HttpRequestException,让异常能正常传递到Polly策略:
private async Task<string> RetryProcessRequest() { // 无额外异常捕获逻辑,确保异常向上传递 var token = await _tokenInfrastructure.GetTokenAsync(); return await _timezoneInfrastructure.ProcessRequest(token); }
4. 添加重试日志验证
在策略中添加OnRetryAsync回调,直接查看Polly是否触发了重试:
_retryPolicy = Policy .Handle<HttpRequestException>() .Or<CustomException>() .OrResult<string>(response => !string.IsNullOrEmpty(response)) .WaitAndRetryAsync( delay, (exception, timeSpan, retryCount, context) => { Console.WriteLine($"触发第{retryCount}次重试,原因:{exception?.Message ?? "非空返回结果"}"); return Task.CompletedTask; });
运行测试时查看控制台输出,确认异常场景下是否有重试日志生成。
5. 升级Polly版本
你使用的Polly 7.2.3版本较旧,存在部分异步异常处理的已知问题。建议升级到7.x系列的最新稳定版本(如7.2.4及以上),修复潜在的框架bug。
调整后的测试代码
[Fact] public async Task ProcessRequest_Throws() { string errors = _fixture.Create<string>(); var token = _fixture.Create<string>(); var expectedReceivedCalls = 4; // 使用ThrowsAsync模拟异步异常 _tokenInfrastructure.GetTokenAsync().ThrowsAsync(new HttpRequestException()); _timezoneInfrastructure.ProcessRequest(token).Returns(errors); await _timezoneOrchestration.Awaiting(o => o.ProcessRequest()).Should() .ThrowAsync<HttpRequestException>(); await _tokenInfrastructure.Received(expectedReceivedCalls).GetTokenAsync(); await _timezoneInfrastructure.Received(expectedReceivedCalls).ProcessRequest(Arg.Any<string>()); }
内容的提问来源于stack exchange,提问作者Maruf
相关产品推荐
相关产品推荐

