У меня есть простой 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");
}
Есть идеи, что мне здесь не хватает?
Спасибо!
У меня была такая же проблема при реализации пользовательского фильтра проверки, и я обнаружил, что атрибут APIController выполняет автоматическую проверку состояния модели, поэтому либо удалите атрибут apiController из контроллера, либо лучший способ отключить поведение по умолчанию, установив для параметра SuppressModelStateInvalidFilter значение true. Вы можете установить для этого параметра значение true в методе ConfigureServices. Нравиться,
public void ConfigureServices(IServiceCollection services)
{
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
}
Любые мысли о том, как вызвать фильтр во время тестирования? (Отредактировал мой вопрос, чтобы попытаться подчеркнуть аспект тестирования)
Я взломал его после прочтения этого: 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);
}
Надеюсь, кому-то это будет полезно :-)
Привет. Моя проблема связана со временем тестирования, мой приведенный выше код работает во время выполнения на .net core 2.2 (который обрабатывает фильтры немного иначе, чем его предшественники).