Тестирование атрибута TypeFilter с внедрением зависимостей - PullRequest
1 голос
/ 09 апреля 2019

У меня есть простой 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)
        {
        }
    }
}

Я пытаюсь проверить это с помощью xUnit, используя FakeController, который выглядит следующим образом:

[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 голосов
/ 09 апреля 2019

Я взломал его после прочтения: 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);
}

Надеюсь, это будет кому-то полезно: -)

0 голосов
/ 09 апреля 2019

У меня была такая же проблема при реализации пользовательского фильтра проверки, и я обнаружил, что атрибут APIController выполняет автоматическую проверку состояния модели, поэтому либо удалите атрибут apiController из Controller, либо лучше отключить поведение по умолчанию, установив для параметра SuppressModelStateInvalidFilter значение true. Вы можете установить для этой опции значение true в методе ConfigureServices. Мол,

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