C# 我如何对用ServiceFilterAttribute(自定义ActionFilter实现)修饰的控制器进行单元测试

C# 我如何对用ServiceFilterAttribute(自定义ActionFilter实现)修饰的控制器进行单元测试,c#,unit-testing,asp.net-core,.net-core,custom-action-filter,C#,Unit Testing,Asp.net Core,.net Core,Custom Action Filter,总结: 我正在尝试使用ActionFilter实现测试控制器 单元测试失败,因为在单元测试中未调用ActionFilter 通过邮递员进行的测试达到了预期效果,取得了正确的结果 控制器可以这样测试吗?还是应该转到集成测试 细分: 我能够在单元测试中单独测试ActionFilter,我想做的是在单元测试中测试控制器 操作筛选器如下所示: public class ValidateEntityExistAttribute<T> : IActionFilter whe

总结:

  • 我正在尝试使用ActionFilter实现测试控制器
  • 单元测试失败,因为在单元测试中未调用ActionFilter
  • 通过邮递员进行的测试达到了预期效果,取得了正确的结果
  • 控制器可以这样测试吗?还是应该转到集成测试
细分:

我能够在单元测试中单独测试ActionFilter,我想做的是在单元测试中测试控制器

操作筛选器如下所示:

 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);
        }
    }
[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();
}
这就是InitializeController方法的样子,我留下了注释行,这样我试过的代码就可以看到它了,注释的代码都不起作用。 我模仿并使用默认类

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;
    }
private Meeting Controller InitializeControl(AppDbContext AppDbContext)
{
var_controller=新的会议控制器(appDbContext);
var spf=new DefaultServiceProviderFactory(new ServiceProviderOptions{ValidateOnBuild=true,ValidateScopes=true});
var sc=spf.CreateBuilder(new ServiceCollection());
sc.AddMvc();
sc.AddControllers();
//(配置=>
//{
//添加(新的ValidateModelStateAttribute());
//添加(新的ValidateEntityExistAttribute(appDbContext));
//});
sc.AddTransient();
sc.AddTransient();
var sp=sc.BuildServiceProvider();
//var mockHttpContext=new Mock();
var httpContext=新的DefaultHttpContext
{
RequestServices=sp
};
//mockHttpContext.Setup(cx=>cx.RequestServices).Returns(sp);
//var contDesc=新的ControllerActionDescriptor();
//var context=new ControllerContext();
//var context=newcontrollercontext(newactioncontext(mockHttpContext.Object,newroutedata(),contDesc));
//context.HttpContext=mockHttpContext.Object;
//context.HttpContext=HttpContext;
//_controller.ControllerContext=上下文;
_controller.ControllerContext.HttpContext=HttpContext;
返回控制器;
}
我遇到的问题是,在运行单元测试时,从未调用ActionFilter实现,因此中断了测试,因为
var entity=HttpContext.Items[“entity_found”]作为会议始终为空!更准确地说,
HttpContext.Items
始终为空

ActionFilter中的断点永远不会被命中

当通过邮递员对其进行测试时,一切正常,并且达到了断点


有没有办法以这种方式将控制器作为单元测试进行测试,或者现在该测试应该转移到集成吗?

谢谢@Fei Han的链接

正如microsoft文档中所述,这是经过设计的

单元测试控制器 设置控制器动作的单元测试,以 关注控制器的行为。控制器单元测试 过滤器、路由和模型绑定等场景。检验 涵盖组件之间的交互,这些组件共同响应 请求由集成测试处理

因此,测试应该转向集成测试

在这个特定场景中,
GetById(intid)
方法没有详细的实现,对它进行单元测试几乎没有任何价值


如果
GetById(int-id)
方法有一个更复杂的实现,或者如果ActionFilter没有阻止进一步的处理,那么应该模拟
HttpContext.Items[“entity\u found”]

谢谢@Fei Han的链接

正如microsoft文档中所述,这是经过设计的

单元测试控制器 设置控制器动作的单元测试,以 关注控制器的行为。控制器单元测试 过滤器、路由和模型绑定等场景。检验 涵盖组件之间的交互,这些组件共同响应 请求由集成测试处理

因此,测试应该转向集成测试

在这个特定场景中,
GetById(intid)
方法没有详细的实现,对它进行单元测试几乎没有任何价值


如果
GetById(int-id)
方法具有更复杂的实现,或者如果ActionFilter没有阻止进一步的处理,则应模拟
HttpContext.Items[“entity_found”]

请注意,a避免了过滤器、路由和模型绑定等场景。对于单元测试,您不需要在特定的控制器操作上测试过滤器,您可以测试过滤器本身。谢谢@Fei Han!这就清楚了为什么过滤器不会被触发。我想,对于一个具有这个小实现的控制器,为它编写单元测试几乎没有任何价值。请注意,a避免了过滤器、路由和模型绑定等场景。对于单元测试,您不需要在特定的控制器操作上测试过滤器,您可以测试过滤器本身。谢谢@Fei Han!这就清楚了为什么过滤器不会被触发。我想,对于一个带有这个小实现的控制器,为它编写单元测试几乎没有任何价值。
[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();
}
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;
    }