You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写单元测试验证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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:52:27