views:

50

answers:

1

I recently did some refactoring of my mvc application and realized that there are alot of static views returned. Instead of having multiple controllers with action results that only return a view I decided to create one controller that returns the static views if they exists and throws a 404 error if the view doesn't exist.

public ActionResult Index(string name)
{
    ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, name, null);

    if (result.View == null)
        ThrowNotFound("Page does not exists.");

    return View(name);
}

My question is what is the right way of unit testing this? I tried the following code but the error I get is "The RouteData must contain an item named 'controller' with a non-empty string value".

[Theory]
[InlineData("ContactUs")]
public void Index_should_return_view_if_view_exists(string name)
{
    controller = new ContentController();
    httpContext = controller.MockHttpContext("/", "~/Content/Index", "GET"); ;

    var result = (ViewResult)controller.Index(name);

    Assert.NotNull(result.View);
}

My intention was for the unit test to go out and fetch the real view. Then I started to wonder if I should mock the ViewEngines with SetupGet for FindView and create two tests, where the second one tests that the not found exception is thrown if the view is null.

What is the correct way of testing this functionality? Any pointers, sample code or blog posts would be helpful.

Thanks

A: 

You should create a mocked view engine and put it in the collection:

[Theory]
[InlineData("ContactUs")]
public void Index_should_return_view_if_view_exists(string name)
{
    var mockViewEngine = MockRepository.GenerateStub<IViewEngine>();
    // Depending on what result you expect you could set the searched locations
    // and the view if you want it to be found
    var result = new ViewEngineResult(new [] { "location1", "location2" });
    // Stub the FindView method
    mockViewEngine
        .Stub(x => x.FindView(null, null, null, false))
        .IgnoreArguments()
        .Return(result);
    // Use the mocked view engine instead of WebForms
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(mockViewEngine);

    controller = new ContentController();

    var actual = (ViewResult)controller.Index(name);

    Assert.NotNull(actual.View);
}
Darin Dimitrov
Darin, thanks! Can you clarify the "locations1", "locations2"? Let's assume my view is in ~Views/Content/ContactUs.aspx and I would like the above test to find it. How would I setup the ViewEngineResult accordingly?
Thomas
No matter where your view is. In a unit test project there's no view. That's the whole point of mocking the view engine. `location1` and `location2` are just to make the compiler happy. Not used at all. You could put an empty string collection there. But in the real world they represent the locations that your view engine tried for searching for the view.
Darin Dimitrov
So in your example result.View is null so when the test runs the view will not be found. How would I go about testing the reverse? I want to test if the view was found.
Thomas
If anyone is interested I ended up using the other overload of ViewEngine Result where I mocked IView. If IView is not null then the ViewEngine found the view and the appropriate code for that scenario could be tested.
Thomas