使用依赖关系注入测试 TypeFilterAttribute



我有一个简单的TypeFilterAttribute,称为MyFilter,它利用了底层的依赖注入(.net core 2.2(:

public class MyFilter : TypeFilterAttribute
{
    public MyFilter() :
        base(typeof(MyFilterImpl))
    {
    }
    private class MyFilterImpl : IActionFilter
    {
        private readonly IDependency _dependency;
        public MyFilterImpl(IDependency injected)
        {
            _dependency = injected;
        }
        public void OnActionExecuting(ActionExecutingContext context)
        {
            _dependency.DoThing();
        }
        public void OnActionExecuted(ActionExecutedContext context)
        {
        }
    }
}

我正在尝试使用如下所示的FakeController使用 xUnit 对此进行测试:

[ApiController]
[MyFilter]
public class FakeApiController : ControllerBase
{
    public FakeApiController()
    {
    }
    [HttpGet("ping/{pong}")]
    public ActionResult<string> Ping(string pong)
    {
        return pong;
    }
}

遇到的问题是我似乎无法在测试时触发MyFilter逻辑。到目前为止,这是我的测试方法的样子:

[Fact]
public void MyFilterTest()
{
    IServiceCollection services = new ServiceCollection();
    services.AddScoped<IDependency, InMemoryThing>();
    var provider = services.BuildServiceProvider();
    var httpContext = new DefaultHttpContext();
    httpContext.RequestServices = provider;
    var actionContext = new ActionContext
    {
        HttpContext = httpContext,
        RouteData = new RouteData(),
        ActionDescriptor = new ControllerActionDescriptor()
    };
    var controller = new FakeApiController()
    {
        ControllerContext = new ControllerContext(actionContext)
    };
    var result = controller.Ping("hi");
}

知道我在这里错过了什么吗?

谢谢!

我在读完这篇文章后破解了它:https://stackoverflow.com/a/50817536/1403748

虽然这并不能直接回答我的问题,但它让我走上了正确的轨道。关键是类MyFilterImpl的范围界定。通过提升它,过滤器的测试问题可以与它增强的控制器分开。

public class MyFilter : TypeFilterAttribute
{
    public MyFilter() : base(typeof(MyFilterImpl))
    {
    }
}
public class MyFilterImpl : IActionFilter
{
    private readonly IDependency _dependency;
    public MyFilterImpl(IDependency injected)
    {
        _dependency = injected;
    }
    public void OnActionExecuting(ActionExecutingContext context)
    {
        _dependency.DoThing();
    }
    public void OnActionExecuted(ActionExecutedContext context)
    {
    }
}

完成此操作后,只需实例化ActionExecutingContext并直接在筛选器实例上调用.OnActionExecuting()即可。我最终编写了一个帮助程序方法,该方法允许将IServiceCollection传递到其中(确保测试服务/数据可以在测试时注入是必需的(:

/// <summary>
/// Triggers IActionFilter execution on FakeApiController
/// </summary>
private static async Task<HttpContext> SimulateRequest(IServiceCollection services, string methodName)
{
    var provider = services.BuildServiceProvider();
    // Any default request headers can be set up here
    var httpContext = new DefaultHttpContext()
    {
        RequestServices = provider
    };
    // This is only necessary if MyFilterImpl is examining the Action itself
    MethodInfo info = typeof(FakeApiController)
        .GetMethods(BindingFlags.Public | BindingFlags.Instance)
        .FirstOrDefault(x => x.Name.Equals(methodName));
    var actionContext = new ActionContext
    {
        HttpContext = httpContext,
        RouteData = new RouteData(),
        ActionDescriptor = new ControllerActionDescriptor()
        {
            MethodInfo = info
        }
    };
    var actionExecutingContext = new ActionExecutingContext(
        actionContext,
        new List<IFilterMetadata>(),
        new Dictionary<string, object>(),
        new FakeApiController()
        {
            ControllerContext = new ControllerContext(actionContext),
        }
    );
    var filter = new MyFilterImpl(provider.GetService<IDependency>());
    filter.OnActionExecuting(actionExecutingContext);
    await (actionExecutingContext.Result?.ExecuteResultAsync(actionContext) ?? Task.CompletedTask);
    return httpContext;
}

测试方法本身就是这样:

[Fact]
public void MyFilterTest()
{
    IServiceCollection services = new ServiceCollection();
    services.AddScoped<IDependency, MyDependency>();
    var httpContext = await SimulateRequest(services, "Ping");
    Assert.Equal(403, httpContext.Response.StatusCode);
}

希望这对其他人有用:-(

我在实现自定义验证过滤器时遇到了同样的问题,我发现 APIController 属性执行自动模型状态验证,因此要么从控制器中删除 apiController 属性,要么通过将"抑制模型状态无效筛选器"选项设置为 true 来禁用默认行为的更好方法。可以在配置服务方法中将此选项设置为 true。喜欢

public void ConfigureServices(IServiceCollection services)
{
services.Configure<ApiBehaviorOptions>(options =>
{
    options.SuppressModelStateInvalidFilter = true;
});
}

最新更新