首页 > 解决方案 > 测试 .NET Core 控制器视图组件

问题描述

在 .NET Core 中,我们有一个控制器操作,它调用具有依赖注入的 ViewComponent,遵循此处的 Microsoft 文档中的模式。

如果我们有一个返回渲染视图的控制器操作,如下所示:

public class MyController : Controller
{
    private readonly IToDoContext _todoContext;
    public MyController(IToDoContext todoContext)
    {
        _todoContext = todoContext;
    }

    public IActionResult IndexVC()
    {
        return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
    }
}

还有一个 ViewComponent 具有这样的依赖注入:

public class PriorityListViewComponent : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityListViewComponent(ToDoContext context)
    {
        db = context;
    }

    public async Task<IViewComponentResult> InvokeAsync(int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }
    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db.ToDo.Where(x => x.IsDone == isDone && x.Priority <= maxPriority).ToListAsync();
    }
}

我们如何在控制器动作单元测试中将 ToDoContext 注入到 ViewComponent 中?到目前为止,result.Model 始终为空,因为似乎没有办法将 ToDoContext db 注入到 Controller 中,然后再注入到 ViewComponent 中。

[Fact]
public void Should_return_viewcomponent()
{
    var mockToDoContext = new Mock<IToDoContext>();
    mockToDoContext.Setup(m => m.Get()).ReturnsAsync(new ToDoContext());

    var sut = new MyController(mockToDoContext.Object);

    var result = sut.PriorityListViewComponent("myparams") as ViewComponentResult;
    // ToDoContext db is never set, so the result.Model is always null
}

标签: c#asp.net-coreasp.net-core-2.0

解决方案


仅供参考:我认为微软的例子是错误的。一旦我将 View Component 更改为 View 并将数据从 View Component 代码后面移到 Controller 中,那么一切都变得容易测试和实现 Bad Request 逻辑,等等。只要记住将 View Component 移出 View Component 目录并在视图中将 layout 设置为 null 即可。

控制器应该看起来更像这样:

public class MyController : Controller
{
    private readonly IToDoContext _db;
    public MyController(IToDoContext db)
    {
        _db = db;
    }

    public async Task<IActionResult> IndexVC("PriorityList", new { maxPriority, isDone = false })
        {
            if (string.IsNullOrWhiteSpace(maxPriority))
                return BadRequest($"maxPriority cannot be empty");

            var model = await InvokeAsync(maxPriority, isDone);

            if (model == null)
            {
                return BadRequest($"model not found");
            }

            return View(model);
        }

    public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }
        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return _db.ToDo.Where(x => x.IsDone == isDone &&
                                 x.Priority <= maxPriority).ToListAsync();
        }
}

单元测试看起来像:

[Fact]
public void Should_return_viewcomponent()
{
    var mockToDoContext = new Mock<IToDoContext>();
    mockToDoContext.Setup(m => m.Get())
        .ReturnsAsync(new ToDoContext());

    var sut = new MyController(mockToDoContext.Object);

    var result = await sut.PriorityList("myparams") as ViewResult;
    result.Model.ShouldNotBeNull();
    var resultModel = result.Model as PriorityListModel;
    resultModel.ShouldNotBeNull();
    resultModel.Thing.ShouldBe("whatever");
}

错误请求单元测试如下所示(注意 BadRequestObjectResult):

[Fact]
public async Task Should_return_bad_request_if_result_not_found()
{
    var mockToDoContext = new Mock<IToDoContext>();
    mockToDoContext.Setup(m => m.Get())
        .ReturnsAsync(null);

    var httpContext = new DefaultHttpContext();

    var sut = new MyController(mockToDoContext.Object)
    {
        ControllerContext = new ControllerContext { HttpContext = httpContext }
    };
    var result = await sut.PriorityList("myparams") as BadRequestObjectResult;

    result.ShouldNotBeNull();
    result.StatusCode.ShouldBe(400);
    result.Value.ShouldBe("model not found");
}

推荐阅读