【问题标题】:NUnit testing MVC Controller returns null ViewNUnit 测试 MVC 控制器返回空视图
【发布时间】:2011-10-10 18:36:42
【问题描述】:

我的 HomeController.Index() 操作有效(在正常操作中),但在 NUnit 测试下,返回的 ActionResult (ViewResult) 始终具有空视图和视图名称。

这里是我正在运行的测试(为了便于阅读,浓缩成一个方法)。

  • 我正在使用 Moq、NUnit、Castle.Windsor
  • 结果的模型正确,但没有与结果关联的视图。
  • 除了最后一个断言之外,所有断言都通过了,它引用了 result.View。

为了清晰而重复 - 在正常操作中返回正确的视图。

[Test]
public void WhenHomeControllerIsInstantiated()
{
    Moch mochRepository = new Mock<IRepository>();
    mochRepository.Setup(s => s.Staff.GetStaffByLogonName("twehr"))
                  .Returns(new Staff { StaffID = 5, LogonName = @"healthcare\twehr" });

    IController controller = new HomeController(mochRepository.Object);

    IPrincipal FakeUser = new GenericPrincipal(new GenericIdentity("twehr", "Basic"), null);
    var result = ((HomeController)controller).Index(FakeUser) as ViewResult;

    Assert.IsNotNull(controller);
    Assert.IsInstanceOf(typeof(HomeController), controller);

    Assert.IsInstanceOf(typeof(HomeViewModel), ((ViewResult)result).Model);

    // result.View and result.ViewName are always null
    Assert.AreEqual("Index", result.ViewName);
}

显然,我忽略了测试设置中的某些内容,但找不到。任何帮助表示赞赏。

【问题讨论】:

  • View 和 ViewName 可以为 null,但由于 约定优于配置,它仍然可以工作。我猜操作方法没有明确指定视图或视图名称?
  • 查看 HomeController 的 Index 方法会很有帮助。另外,您是否尝试过调试单元测试并单步执行控制器代码?

标签: asp.net-mvc-3 view controller nunit


【解决方案1】:

result.View 为 null 的原因是你还没有在控制器的上下文中执行视图结果,你只是在测试中直接调用了 action 方法,它返回了一个 ViewResult 准备好由 MVC 执行框架。

result.ViewName 为 null 的原因是你没有在 action 方法中明确指定它。

MVC 框架在返回的 ViewResult 上调用 ExecuteResult(ControllerContext context),然后填充 ViewName(如果为 null)并通过调用 FindView(context) 搜索要呈现的视图,FindView(context) 填充视图。

看看这里的MVC代码,你可能会更好理解:

// System.Web.Mvc.ViewResultBase
public override void ExecuteResult(ControllerContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException("context");
    }
    if (string.IsNullOrEmpty(this.ViewName))
    {
        this.ViewName = context.RouteData.GetRequiredString("action");
    }
    ViewEngineResult viewEngineResult = null;
    if (this.View == null)
    {
        viewEngineResult = this.FindView(context);
        this.View = viewEngineResult.View;
    }
    TextWriter output = context.HttpContext.Response.Output;
    ViewContext viewContext = new ViewContext(context, this.View, this.ViewData, this.TempData, output);
    this.View.Render(viewContext, output);
    if (viewEngineResult != null)
    {
        viewEngineResult.ViewEngine.ReleaseView(context, this.View);
    }
}

如上所述,如果您在控制器中返回 ViewResult 并明确指定 ViewName,那么您可以在测试中对其进行测试。

例如而不是做

return View(model);

return View("Index", model);

另外,我同意 Zasz 的第一点,你的测试有一些奇怪的断言和大量不必要的强制转换。我发现编写这类测试的最简洁的方式是:

HomeController controller = new HomeController();

ViewResult result = controller.Index() as ViewResult;
Assert.IsNotNull(result); // Asserts that result is of type ViewResult since it will be null otherwise

// TODO: Add assertions on the model
// ...

【讨论】:

  • 我得到了 null ViewResult result = controller.Index() as ViewResult;
【解决方案2】:

你的测试很奇怪。对我来说,这些陈述看起来毫无目的:

Assert.IsNotNull(controller);
Assert.IsInstanceOf(typeof(HomeController), controller);

你在这里测试什么?在使用上面的界面时,这里的投射是什么:

((HomeController)controller)

那为什么要使用上面的界面呢?当您可以根据您的情况将 Index 方法更改为返回 ViewResult 时,为什么它会返回 ActionResult (我猜)?具体来说,只有当 Action 可以返回一种以上的结果时才使用超类 ActionResult。你可以避免这个演员((ViewResult)result)

作为对您问题的回答:

result.ViewName 等,仅当您像这样显式调用控制器中的 View() 方法时才会填充:

View("Index", model)

如果您只调用 View(),您将依赖于框架来根据约定呈现正确的视图 - 而您 不需要 需要测试框架功能 - 所以只检查模型。信任 MVC 来呈现正确的视图 - MVC 不会到处填充 ViewName 属性,而是检查该属性是否为空,如果是,则继续并使用约定来呈现视图,否则呈现您指定的内容。

【讨论】:

  • 我的控制器确实调用了视图(“索引”,模型)。由于一个动作可以调用它喜欢的任何视图,因此测试是否返回了正确的视图是合理的。这是测试的目的。但是,正如我在原始帖子中所述,它不会返回正在测试的视图或视图名称。它确实会在正常的实时操作中返回视图。我的问题是,现在仍然是,为什么 View 和 ViewName 没有被填充?我需要能够测试是否返回了正确的视图。
  • 真的喜欢这个答案。如果重点是测试 ASP.NET MVC 的 View() 函数,则需要单独完成。单元测试应该测试我们的代码,而不是 MVC。因此,可以通过分解和测试部件并完全从单元测试中省略 View() 调用来避免这个问题。
猜你喜欢
  • 2021-08-16
  • 1970-01-01
  • 2018-04-27
  • 2013-12-03
  • 2017-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多