使用依赖注入测试TypeFilterAttribute

时间:2019-04-08 21:43:00

标签: c# asp.net-core dependency-injection

我有一个简单的TypeFilterAttribute,名为MyFilter,它在后台(.net核心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");
}

知道我在这里缺少什么吗?

谢谢!

2 个答案:

答案 0 :(得分:0)

在实现自定义验证过滤器时,我遇到了同样的问题,我发现APIController属性执行自动模型状态验证,因此 从Controller中删除apiController属性,或者通过将SuppressModelStateInvalidFilter选项设置为true来禁用默认行为的更好方法。您可以在ConfigureServices方法中将此选项设置为true。喜欢,

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

答案 1 :(得分:0)

我在阅读以下内容后将其破解: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);
}

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