[英]How do I unit test a controller that is decorated with the ServiceFilterAttribute, custom ActionFilter implementation
摘要:
細分:
我可以在單元測試中自行測試 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.