[英]How do I unit test a controller that is decorated with the ServiceFilterAttribute, custom ActionFilter implementation
Summary :摘要:
Breakdown :细分:
I'm able to test the ActionFilter on its own in a unit test, what I would like to do is test the controller in a unit test.我可以在单元测试中自行测试 ActionFilter,我想做的是在单元测试中测试 controller。
The action filter looks like this:动作过滤器如下所示:
public class ValidateEntityExistAttribute<T> : IActionFilter
where T : class, IEntityBase
{
readonly AppDbContext _appDbContext;
public ValidateEntityExistAttribute(AppDbContext appDbContext)
{
this._appDbContext = appDbContext;
}
public void OnActionExecuted(ActionExecutedContext context)
{}
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ActionArguments.ContainsKey("id"))
{
context.Result = new BadRequestObjectResult("The id must be passed as parameter");
return;
}
int id = (int)context.ActionArguments["id"];
var foundEntity = _appDbContext.Set<T>().Find(id);
if (foundEntity == null)
context.Result = new NotFoundResult();
else
context.HttpContext.Items.Add("entity_found", foundEntity);
}
}
The ActionFilter implementation is added to the services in the startup file在启动文件中将 ActionFilter 实现添加到服务中
ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<ValidateEntityExistAttribute<Meeting>>();
...
}
The filter can be applied to any controller method that needs to check if an entity exists.过滤器可以应用于需要检查实体是否存在的任何 controller 方法。 ie the GetById method.即GetById 方法。
[HttpGet("{id}")]
[ServiceFilter(typeof(ValidateEntityExistAttribute<Meeting>))]
public async Task<ActionResult<MeetingDto>> GetById(int id)
{
var entity = HttpContext.Items["entity_found"] as Meeting;
await Task.CompletedTask;
return Ok(entity.ConvertTo<MeetingDto>());
}
In the xUnit test I have set up the test to test the controller like this:在 xUnit 测试中,我设置了测试来测试 controller,如下所示:
[Fact]
public async Task Get_Meeting_Record_By_Id()
{
// Arrange
var _AppDbContext = AppDbContextMocker.GetAppDbContext(nameof(Get_All_Meeting_Records));
var _controller = InitializeController(_AppDbContext);
//Act
var all = await _controller.GetById(1);
//Assert
Assert.Equal(1, all.Value.Id);
//clean up otherwise the other test will complain about key tracking.
await _AppDbContext.DisposeAsync();
}
and this is what the InitializeController method look like, I left the commented lines so that it is visible to what I have tried, none of the commented code worked.这就是 InitializeController 方法的样子,我留下了注释行,以便它对我所尝试的内容可见,注释代码都不起作用。 I mocked and used the default classes.我嘲笑并使用了默认类。
private MeetingController InitializeController(AppDbContext appDbContext)
{
var _controller = new MeetingController(appDbContext);
var spf = new DefaultServiceProviderFactory(new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true });
var sc = spf.CreateBuilder(new ServiceCollection());
sc.AddMvc();
sc.AddControllers();
//(config =>
//{
// config.Filters.Add(new ValidateModelStateAttribute());
// config.Filters.Add(new ValidateEntityExistAttribute<Meeting>(appDbContext));
//});
sc.AddTransient<ValidateModelStateAttribute>();
sc.AddTransient<ValidateEntityExistAttribute<Meeting>>();
var sp = sc.BuildServiceProvider();
//var mockHttpContext = new Mock<HttpContext>();
var httpContext = new DefaultHttpContext
{
RequestServices = sp
};
//mockHttpContext.Setup(cx => cx.RequestServices).Returns(sp);
//var contDesc = new ControllerActionDescriptor();
//var context = new ControllerContext();
//var context = new ControllerContext(new ActionContext(mockHttpContext.Object, new RouteData(), contDesc));
//context.HttpContext = mockHttpContext.Object;
//context.HttpContext = httpContext;
//_controller.ControllerContext = context;
_controller.ControllerContext.HttpContext = httpContext;
return _controller;
}
The issues I have is that when running the unit test the ActionFilter implementation is never invoked, thus breaking the test because var entity = HttpContext.Items["entity_found"] as Meeting;
我遇到的问题是,在运行单元测试时,ActionFilter 实现从未被调用,因此破坏了测试,因为var entity = HttpContext.Items["entity_found"] as Meeting;
in the controller is always null !在 controller 中总是null ! more accurately HttpContext.Items
is always null.更准确地说, HttpContext.Items
总是 null。
The break point in the ActionFilter never gets hit. ActionFilter 中的断点永远不会被击中。
When this is tested via postman it all works as expected, and the break point gets hit当通过 postman 进行测试时,一切都按预期工作,并且断点被击中
Is there a way to test the controller as a unit test this way, or should this test now just move to integration ?有没有办法以这种方式将 controller 作为单元测试进行测试,或者该测试现在是否应该转移到集成?
Thank you @Fei Han for the link about unit testing controllers .感谢@Fei Han 提供有关单元测试控制器的链接。
As it turns out this is by design, as stated in the microsoft documentation事实证明这是设计使然,如微软文档中所述
Unit testing controllers Set up unit tests of controller actions to focus on the controller's behavior.单元测试控制器设置 controller 动作的单元测试以关注控制器的行为。 A controller unit test avoids scenarios such as filters, routing, and model binding. controller 单元测试避免了过滤器、路由和 model 绑定等场景。 Tests that cover the interactions among components that collectively respond to a request are handled by integration tests.涵盖共同响应请求的组件之间的交互的测试由集成测试处理。
So the test should move to integration testing.所以测试应该转向集成测试。
In this particular scenario , where there is no detailed implementation for the GetById(int id)
method, there is almost no value in doing a unit test for it.在这个特殊的场景中,没有GetById(int id)
方法的详细实现,对其进行单元测试几乎没有任何价值。
If the GetById(int id)
method had a more complex implementation, or if the ActionFilter did not prevent further processing, the HttpContext.Items["entity_found"]
should be mocked.如果GetById(int id)
方法具有更复杂的实现,或者如果ActionFilter没有阻止进一步处理, HttpContext.Items["entity_found"]
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.