如何用XUnit测试含功能开关的.NET Core控制器
.NET Core功能开关控制器单元测试问题修正方案
问题描述
基于.NET Core Feature Management实现了带自定义过滤器的功能开关控制器,实际运行时开关关闭会返回404,但单元测试中直接调用控制器Action方法时,即使模拟IFeatureManagerSnapshot返回关闭状态,也不会触发404逻辑,导致测试失败。
原因分析
[FeatureGate]是ASP.NET Core的动作过滤器,其权限校验逻辑依赖MVC管道的过滤器执行阶段。直接实例化控制器并调用Action方法时,并不会触发完整的MVC过滤器管道,因此[FeatureGate]的校验逻辑根本没有执行,自然不会返回404。
修正方案
需要手动触发FeatureGateAuthorizationFilter的校验逻辑,模拟MVC管道的过滤器执行流程。以下是修正后的单元测试代码:
修正后的测试类
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.FeatureManagement.Mvc; using Moq; using Xunit; [Collection("UnitTests")] public class FeatureToggleSampleControllerShould { private readonly Mock<IFeatureManagerSnapshot> _mockFeatureManager; private readonly FeatureToggleSampleController _controller; public FeatureToggleSampleControllerShould() { _mockFeatureManager = new Mock<IFeatureManagerSnapshot>(); // 修正构造函数参数不匹配问题:控制器仅依赖IFeatureManagerSnapshot _controller = new FeatureToggleSampleController(_mockFeatureManager.Object); } [Fact] public async Task Return_String_If_Toggle_Enabled() { // 模拟开关开启状态 _mockFeatureManager .Setup(x => x.IsEnabledAsync(nameof(WidgetToggles.sampleToggleName))) .Returns(Task.FromResult(true)); // 调用Action(开关开启时过滤器允许执行) var result = _controller.ShowSampleToggle(new MeFunctionsRequest()); Assert.Equal("sample toggle is enabled", result); } [Fact] public async Task Return_404_If_Toggle_Disabled() { // 模拟开关关闭状态 _mockFeatureManager .Setup(x => x.IsEnabledAsync(nameof(WidgetToggles.sampleToggleName))) .Returns(Task.FromResult(false)); // 实例化FeatureGate过滤器 var featureGateFilter = new FeatureGateAuthorizationFilter( _mockFeatureManager.Object, new FeatureGateAttribute(nameof(WidgetToggles.sampleToggleName))); // 构造过滤器上下文,模拟MVC管道环境 var actionContext = new ActionContext { Controller = _controller, HttpContext = new DefaultHttpContext(), ActionDescriptor = new Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor() }; var filterContext = new AuthorizationFilterContext(actionContext, new List<IFilterMetadata>()); // 执行过滤器校验逻辑 await featureGateFilter.OnAuthorizationAsync(filterContext); // 验证结果是否为404 Assert.IsType<NotFoundResult>(filterContext.Result); } }
关键修正点说明
- 修正构造函数参数错误:原测试中错误传入
TestHelper.Appsettings,实际控制器仅依赖IFeatureManagerSnapshot,需匹配构造函数参数。 - 手动触发过滤器逻辑:直接实例化
FeatureGateAuthorizationFilter,模拟过滤器上下文并调用OnAuthorizationAsync方法,触发[FeatureGate]的校验逻辑。 - 结果类型验证:开关关闭时,过滤器会将上下文结果设置为
NotFoundResult,直接验证该类型即可,无需通过捕获异常判断。
补充建议
如果希望更贴近真实请求场景,可使用WebApplicationFactory编写集成测试,自动触发完整MVC管道:
public class FeatureToggleIntegrationTests : IClassFixture<WebApplicationFactory<Startup>> { private readonly WebApplicationFactory<Startup> _factory; public FeatureToggleIntegrationTests(WebApplicationFactory<Startup> factory) { _factory = factory.WithWebHostBuilder(builder => { builder.ConfigureServices(services => { // 替换IFeatureManagerSnapshot为模拟实现 var mockFeatureManager = new Mock<IFeatureManagerSnapshot>(); mockFeatureManager.Setup(x => x.IsEnabledAsync(nameof(WidgetToggles.sampleToggleName))) .Returns(Task.FromResult(false)); services.AddSingleton(mockFeatureManager.Object); }); }); } [Fact] public async Task Get_TogglesDemo_Returns404_WhenToggleDisabled() { var client = _factory.CreateClient(); var response = await client.GetAsync("/toggles/demo"); Assert.Equal(System.Net.HttpStatusCode.NotFound, response.StatusCode); } }
内容的提问来源于stack exchange,提问作者dot
相关产品推荐
相关产品推荐

