我有一个简单的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;
});
}