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

如何用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);
    }
}

关键修正点说明

  1. 修正构造函数参数错误:原测试中错误传入TestHelper.Appsettings,实际控制器仅依赖IFeatureManagerSnapshot,需匹配构造函数参数。
  2. 手动触发过滤器逻辑:直接实例化FeatureGateAuthorizationFilter,模拟过滤器上下文并调用OnAuthorizationAsync方法,触发[FeatureGate]的校验逻辑。
  3. 结果类型验证:开关关闭时,过滤器会将上下文结果设置为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 13:36:30