.NET Core WebAPI中Polly重试策略的XUnit+Moq单元测试咨询
用XUnit+Moq测试Polly重试逻辑及GetProducts接口
核心思路
测试的关键是模拟服务故障场景,验证Polly是否按配置的次数重试,最终在服务恢复时返回正确结果;同时覆盖正常调用无需重试的场景,确保逻辑正确。
1. 直接测试IProductService的重试逻辑
下面是针对Service层的测试示例,假设你对IProductService.GetProductsAsync()配置了针对HttpRequestException的3次重试策略:
using Xunit; using Moq; using Polly; using Polly.Retry; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; public class ProductServiceRetryTests { private readonly Mock<IProductService> _productServiceMock; private readonly AsyncRetryPolicy _retryPolicy; public ProductServiceRetryTests() { _productServiceMock = new Mock<IProductService>(); // 复用生产环境的重试规则:3次重试,每次间隔100ms,仅处理HttpRequestException _retryPolicy = Policy .Handle<HttpRequestException>() .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromMilliseconds(100)); } [Fact] public async Task GetProducts_WhenServiceFails3TimesThenSucceeds_ShouldRetry3Times() { int callCount = 0; // 配置Mock:前3次调用抛异常,第4次返回正常数据 _productServiceMock.Setup(s => s.GetProductsAsync()) .ReturnsAsync(() => { callCount++; if (callCount <= 3) throw new HttpRequestException("临时不可用"); return new List<Product> { new Product { Id = 1, Name = "测试商品" } }; }); // 执行被重试策略包裹的Service调用 var result = await _retryPolicy.ExecuteAsync(async () => await _productServiceMock.Object.GetProductsAsync()); // 验证返回结果正确 Assert.Single(result); Assert.Equal("测试商品", result[0].Name); // 验证总调用次数:3次重试+1次成功=4次 _productServiceMock.Verify(s => s.GetProductsAsync(), Times.Exactly(4)); } [Fact] public async Task GetProducts_WhenServiceSucceedsImmediately_ShouldNotRetry() { // 配置Mock:第一次调用就返回成功 _productServiceMock.Setup(s => s.GetProductsAsync()) .ReturnsAsync(new List<Product> { new Product { Id = 2, Name = "直接成功商品" } }); var result = await _retryPolicy.ExecuteAsync(async () => await _productServiceMock.Object.GetProductsAsync()); Assert.Single(result); // 验证仅调用1次 _productServiceMock.Verify(s => s.GetProductsAsync(), Times.Once); } } // 你的基础接口和实体定义(示例) public interface IProductService { Task<List<Product>> GetProductsAsync(); } public class Product { public int Id { get; set; } public string Name { get; set; } }
关键说明
- Mock行为控制:通过计数器
callCount精准控制Mock的返回/抛出逻辑,模拟服务从故障到恢复的过程。 - 策略对齐:测试中使用和生产环境完全一致的重试策略,保证测试结果能真实反映实际运行情况。
- 次数验证:用
Mock.Verify直接检查方法调用次数,确认重试逻辑生效。
2. 测试WebAPI Controller层的GetProducts接口
如果要测试Controller的接口(而非直接测试Service),需要确保Controller使用的是带重试策略包裹的Service实例,测试示例如下:
using Xunit; using Moq; using Polly; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; public class ProductsControllerTests { private readonly Mock<IProductService> _productServiceMock; private readonly AsyncRetryPolicy _retryPolicy; private readonly ProductsController _controller; public ProductsControllerTests() { _productServiceMock = new Mock<IProductService>(); _retryPolicy = Policy .Handle<HttpRequestException>() .WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(100)); // 用Polly的PolicyWrapped包裹Mock的Service,模拟DI注入的带重试的实例 var wrappedService = Policy.WrapAsync(_retryPolicy, _productServiceMock.Object); _controller = new ProductsController(wrappedService); } [Fact] public async Task GetProducts_WhenServiceRecoversAfterRetries_ShouldReturnOk() { int callCount = 0; _productServiceMock.Setup(s => s.GetProductsAsync()) .ReturnsAsync(() => { callCount++; if (callCount <= 3) throw new HttpRequestException(); return new List<Product> { new Product { Id = 1, Name = "Controller测试商品" } }; }); // 调用Controller接口 var result = await _controller.GetProducts(); // 验证返回类型和数据 var okResult = Assert.IsType<OkObjectResult>(result); var products = Assert.IsType<List<Product>>(okResult.Value); Assert.Single(products); // 验证Service被调用4次 _productServiceMock.Verify(s => s.GetProductsAsync(), Times.Exactly(4)); } } // 你的Controller定义(示例) public class ProductsController : ControllerBase { private readonly IProductService _productService; public ProductsController(IProductService productService) { _productService = productService; } [HttpGet("getProducts")] public async Task<IActionResult> GetProducts() { var products = await _productService.GetProductsAsync(); return Ok(products); } }
关键说明
- PolicyWrapped实例:通过
Policy.WrapAsync将Mock的Service和重试策略包裹,模拟生产环境中DI注入的带重试的Service实例,确保Controller使用的逻辑和生产一致。 - 结果验证:不仅要验证重试次数,还要验证Controller返回的
IActionResult类型正确,数据符合预期。
内容的提问来源于stack exchange,提问作者itaustralia
相关产品推荐
相关产品推荐

