开发者

NUnit testing MVC Controller returns null View

开发者 https://www.devze.com 2023-04-12 05:29 出处:网络
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.

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.


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


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号