如何编写单元测试验证Polly Resilience Pipeline是否被调用
问题描述
我正在使用Polly(8.0.0)为调用D365服务的API添加弹性能力,相关代码如下:
public class MyApi { private readonly ResiliencePipeline pipeline; private readonly IOrganizationService service; public MyApi(ResiliencePipelineProvider<string> provider, IOrganizationService service) { this.pipeline = provider.GetPipeline("retry-pipeline"); this.service = service; } public List<string> GetSomething(QueryByAttribute query) { var result = this.pipeline.Execute(() => service.RetrieveMultiple(query)); return result.Entities.Cast<string>().ToList(); } }
根据Polly文档,我知道可以通过注入ResiliencePipelineProvider获取管道,也可以Mock它返回ResiliencePipeline.Empty,还能测试管道配置细节。但我想知道**如何测试该管道是否实际被使用?**尝试用Moq Mock ResiliencePipeline时,收到错误提示:
Type to mock (
ResiliencePipeline) must be an interface, a delegate, or a non-sealed, non-static class
解决方法
1. 间接验证:通过依赖服务的调用次数确认管道生效
既然无法直接Mock ResiliencePipeline,可以通过验证被管道包裹的IOrganizationService.RetrieveMultiple方法的调用次数,间接确认管道是否触发(比如重试策略生效时,调用次数会大于1)。
示例测试代码:
[Test] public void GetSomething_WhenServiceFails_RetryPipelineTriggers() { // 准备Mock服务:第一次调用抛异常,第二次正常返回 var mockService = new Mock<IOrganizationService>(); var callCount = 0; mockService.Setup(s => s.RetrieveMultiple(It.IsAny<QueryByAttribute>())) .Returns(() => { callCount++; if (callCount == 1) throw new HttpRequestException("Service unavailable"); return new EntityCollection { Entities = new List<Entity>() }; }); // 构造带重试策略的测试管道(模拟实际配置) var pipeline = new ResiliencePipelineBuilder() .AddRetry(new RetryStrategyOptions { MaxRetryAttempts = 1, // 重试1次,总共调用2次 ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>() }) .Build(); // Mock管道提供者,返回测试管道 var mockProvider = new Mock<ResiliencePipelineProvider<string>>(); mockProvider.Setup(p => p.GetPipeline("retry-pipeline")).Returns(pipeline); // 执行API方法 var api = new MyApi(mockProvider.Object, mockService.Object); api.GetSomething(new QueryByAttribute()); // 验证服务被调用2次,说明重试管道已生效 mockService.Verify(s => s.RetrieveMultiple(It.IsAny<QueryByAttribute>()), Times.Exactly(2)); }
2. 用Polly注册中心跟踪管道执行
Polly 8的ResiliencePipelineRegistry可以替代ResiliencePipelineProvider,测试时可以注册带自定义跟踪逻辑的管道,直接标记管道是否被执行。
示例测试代码:
[Test] public void GetSomething_UsesResiliencePipeline() { var pipelineExecuted = false; // 创建注册中心并添加带跟踪逻辑的管道 var registry = new ResiliencePipelineRegistry<string>(); registry.TryAddPipeline("retry-pipeline", builder => { // 添加自定义策略,执行时标记管道已触发 builder.AddCustomPolicy(_ => new SyncPolicy<EntityCollection>((context, state) => { pipelineExecuted = true; return PolicyResult.Success(context.Callback(context, state)); })); // 保留实际的重试策略配置 builder.AddRetry(new RetryStrategyOptions { MaxRetryAttempts = 1 }); }); var mockService = new Mock<IOrganizationService>(); mockService.Setup(s => s.RetrieveMultiple(It.IsAny<QueryByAttribute>())) .Returns(new EntityCollection()); // 执行API方法 var api = new MyApi(registry, mockService.Object); api.GetSomething(new QueryByAttribute()); // 验证管道已执行 Assert.IsTrue(pipelineExecuted); }
3. 抽象管道依赖(长期优化方案)
定义一个抽象接口包裹ResiliencePipeline的核心能力,让MyApi依赖接口而非具体类型,这样测试时就能直接Mock接口验证调用。
首先定义接口和实现:
public interface IResilienceExecutor { TResult Execute<TResult>(Func<TResult> callback); } public class PollyResilienceExecutor : IResilienceExecutor { private readonly ResiliencePipeline _pipeline; public PollyResilienceExecutor(ResiliencePipeline pipeline) { _pipeline = pipeline; } public TResult Execute<TResult>(Func<TResult> callback) { return _pipeline.Execute(callback); } }
修改MyApi的依赖:
public class MyApi { private readonly IResilienceExecutor _executor; private readonly IOrganizationService _service; public MyApi(IResilienceExecutor executor, IOrganizationService service) { _executor = executor; _service = service; } public List<string> GetSomething(QueryByAttribute query) { var result = _executor.Execute(() => _service.RetrieveMultiple(query)); return result.Entities.Cast<string>().ToList(); } }
测试时直接Mock接口:
[Test] public void GetSomething_CallsResilienceExecutor() { var mockExecutor = new Mock<IResilienceExecutor>(); mockExecutor.Setup(e => e.Execute(It.IsAny<Func<EntityCollection>>())) .Returns(new EntityCollection()); var mockService = new Mock<IOrganizationService>(); var api = new MyApi(mockExecutor.Object, mockService.Object); api.GetSomething(new QueryByAttribute()); // 验证执行器被调用一次 mockExecutor.Verify(e => e.Execute(It.IsAny<Func<EntityCollection>>()), Times.Once); }
内容的提问来源于stack exchange,提问作者Andy Cooke
相关产品推荐
相关产品推荐

