簡體   English   中英

如何對裝飾有 ServiceFilterAttribute、自定義 ActionFilter 實現的 controller 進行單元測試

[英]How do I unit test a controller that is decorated with the ServiceFilterAttribute, custom ActionFilter implementation

摘要

  • 我正在嘗試使用ActionFilter實現測試controller
  • 單元測試失敗,因為在單元測試中沒有調用 ActionFilter。
  • 通過 Postman 進行的測試按預期工作,並獲得了正確的結果。
  • controller 可以像這樣進行測試還是應該轉移到集成測試?

細分

我可以在單元測試中自行測試 ActionFilter,我想做的是在單元測試中測試 controller。

動作過濾器如下所示:

 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);
        }
    }

在啟動文件中將 ActionFilter 實現添加到服務中

ConfigureServices(IServiceCollection services)
{
...
 services.AddScoped<ValidateEntityExistAttribute<Meeting>>();
...
}

過濾器可以應用於需要檢查實體是否存在的任何 controller 方法。 即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>());
    }

在 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();
}

這就是 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;
    }

我遇到的問題是,在運行單元測試時,ActionFilter 實現從未被調用,因此破壞了測試,因為var entity = HttpContext.Items["entity_found"] as Meeting; 在 controller 中總是null 更准確地說, HttpContext.Items總是 null。

ActionFilter 中的斷點永遠不會被擊中。

當通過 postman 進行測試時,一切都按預期工作,並且斷點被擊中

有沒有辦法以這種方式將 controller 作為單元測試進行測試,或者該測試現在是否應該轉移到集成

感謝@Fei Han 提供有關單元測試控制器的鏈接。

事實證明這是設計使然,如微軟文檔中所述

單元測試控制器設置 controller 動作的單元測試以關注控制器的行為。 controller 單元測試避免了過濾器、路由和 model 綁定等場景。 涵蓋共同響應請求的組件之間的交互的測試由集成測試處理。

所以測試應該轉向集成測試。

這個特殊的場景中,沒有GetById(int id)方法的詳細實現,對其進行單元測試幾乎沒有任何價值。

如果GetById(int id)方法具有更復雜的實現,或者如果ActionFilter沒有阻止進一步處理, HttpContext.Items["entity_found"]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM