NUnit 测试 MVC 控制器返回 null 视图

发布于 2024-12-09 05:46:03 字数 1178 浏览 5 评论 0原文

我的 HomeController.Index() 操作有效(在正常操作中),但在 NUnit 测试下,返回的 ActionResult (ViewResult) 始终具有 null View 和 ViewName。

以下是我正在运行的测试(为了便于阅读,将其压缩为一个方法)。

  • 我正在使用 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);
}

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

My HomeController.Index() action works (in normal operation), but under NUnit testing, the ActionResult (ViewResult) that is returned always has a null View and ViewName.

Here are the tests I am running (condensed into a single method for ease of reading).

  • I am using Moq, NUnit, Castle.Windsor
  • The result's Model is correct, but there is no view associated with the result.
  • All assertions pass except the final one, which refers to the result.View.

Repeating for clarity - the correct view is returned in normal operation.

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

Obviously, I am overlooking something in the test setup, but can't find it. Any help is appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

猫性小仙女 2024-12-16 05:46:03

result.View 为 null 的原因是因为您尚未在控制器上下文中执行视图结果,您只需在测试中直接调用操作方法,该方法返回准备好由 MVC 框架执行的 ViewResult 。

result.ViewName 为 null 的原因是您没有在操作方法中显式指定它。

MVC 框架对返回的 ViewResult 调用 ExecuteResult(ControllerContext context),然后填充 ViewName(如果为 null),并通过调用 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);
    }
}

正如 Zasz 上面所说,如果您在控制器中返回一个 ViewResult 并显式指定 ViewName,那么您可以在测试中测试它。

例如,而不是 do

return View(model);

do

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
// ...

The reason result.View is null is because you haven't executed the view result in the context of a controller yet, you've simply called the action method directly in the test which returns a ViewResult ready for Execution by the MVC framework.

The reason result.ViewName is null is that you haven't specified it explicitly in the action method.

The MVC framework calls ExecuteResult(ControllerContext context) on the returned ViewResult, which then populates ViewName (if null) and searches for the view to render by calling FindView(context), which populates the View.

Take a look at the MVC code here and you might understand it slightly better:

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

As Zasz states above, if you return a ViewResult in your controller specifying the ViewName explicitly, then you can test for it in your test.

E.g. rather than do

return View(model);

do

return View("Index", model);

Also, I agree with Zasz on his first point, your test has some strange assertions and lots of casting that isn't necessary. I find the most concise way of writing these sorts of tests goes along the lines of:

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
// ...
眉目亦如画i 2024-12-16 05:46:03

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

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

你在这里测试什么?使用上面的接口时,这里的转换是什么:

((HomeController)controller)

那么为什么要使用上面的接口呢?当您可以根据您的情况更改它以返回 ViewResult 时,为什么 Index 方法返回 ActionResult (我猜测)?具体来说,仅当 Action 可以返回多种结果时才使用超类 ActionResult。您可以避免这种转换 ((ViewResult)result)

并且作为您问题的答案:

仅当您在控制器中显式调用 View() 方法时才会填充 result.ViewName 等,如下所示:

View("Index", model)

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

Your test is very odd. To me these statements look purposeless :

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

What are you testing here anyway? And what is this casting here, while using interface above :

((HomeController)controller)

Why use the interface above then? Why is the Index method returning ActionResult (I'm guessing) when you can change it to return a ViewResult as per your case? Be specific, and use the superclass ActionResult only when the Action can return more than one kind of result. You can avoid this cast ((ViewResult)result)

And as answer to your question :

result.ViewName etc., is populated only if you explicitly call the View() method in controller like this :

View("Index", model)

If you call simply View() you are depending on the framework to render the correct view based on convention - And you DO NOT need to test the framework functionality - so check only the contents of the model. Trust MVC to render the correct view - MVC does not go around populating ViewName property, it instead checks if the property is null, if so, goes ahead and uses the convention to render the view, else renders what you have specified.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文