Tuesday, 6 September 2011

Adventures with the MvcContrib TestHelper

Unit testing a MVC Controller
When unit testing a MVC controller, stop reinventing the wheel and take a look at the MvcContrib TestHelper. Add a reference to the MvcContrib.TestHelper.dll and unit testing different functionality of your controller and its actions should be a walk in the park. Yes, using Moq to build up a mocked object is possible, but since MvcContrib TestHelper supports much of the needed functionality, why not start using it?

How to start using the MvcContrib TestHelper?
Download the MvcContrib TestHelper with the MvcContrib release package from this location: Mvc Contrib

Install the MVC contrib zipped file. Actually there is no installer, just a bunch of DLL files. It is the file MvcContrib.TestHelper.dll that you are interested in. Add this dll as a reference to the Visual Studio 2010 test project in your solution (remember to add this file to your solution by saying "Copy to output folder" and modify your WiX scripts, if needed), then create a new basic unit test or unit test and at the top add the using statement:


using MvcContrib.TestHelper;



Verify that the view result is returned and view name and model type matches in a unit test for a MVC action


Given that we have modified the Index mvc view to be strongly typed by a class Bird in our Models folder, the following code can verify in a single assertion that the returned ActionResult is a ViewResult, that the rendered view name is "Index" and that the model, that is the strongly type that the view is associated with is a given type as follows:


[TestMethod]
public void IndexTestViewResult()
{
var controller = new HomeController();
ActionResult result = controller.Index();
result.AssertViewRendered().ForView("Index").WithViewData();
}



First off, I just new my controller I want to test, HomeController in this case.
Then using the extension methods in MvcContrib.TestHelper I call AsserViewRendere() which just asserts that ActionResult is of type ViewResult. Further, I check with the ForView extension method (take note of the chaining going on here) that the view name is Index. The third part of this single assert checks that the View data is of type Bird.

In this particular example, make note that my Index method looks like the following in my HomeController:


public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";

return View("Index", new Bird { SpeciesName = "Crow",
CanFly = true, WeightInGrams = 400, WingspanInCentimetres = 80 });
}

.
.
.

}


Obviously the Bird class is just a simple sample in this case.. The second argument
could be assigned to variable called model and passed in for cleaner code. The point I want to make here is that when you return a ViewResult, specifically specify the View Name explicitly, as this then can be verified with the ForView extension method in a unit test.

For more advanced controller tests there are lots of functionality in MvcTestContrib.TestHelper DLL to help out. Do not reinvent the wheel to get started with unit testing your controllers, use MvcContrib TestHelper!

Towards more complex controller action tests in MVC


Just to get started with a custom HttpContext, I wanted to test out the TestControllerBuilder class in MvcContrib.TestHelper. With this class, I adjust the unit test for the index action method a bit:


[TestMethod]
public void IndexTestViewResult()
{
TestControllerBuilder builder = new TestControllerBuilder();
var controller = builder.CreateController();
builder.HttpContext.Cache["test"] = "test1";
builder.InitializeController(controller);
ActionResult result = controller.Index();
result.AssertViewRendered().ForView("Index").WithViewData();
}


Now, I use the TestControllerBuilder class to create a controller of type HomeController. The builder is then tested out with setting the HttpContext.Cache to a test key-value pair. Further on, the line above instantiates the HomeController by using the CreateController static generic factory method by passing in the home controller as a generic type argument. Then the controller is initialized with the TestControllerBuilder. The unit test passes. Obviously, this just show how the HttpContext can be mocked using the TestControllerBuilder.

One important note here is the strange error I got thrown at me when running the test the first time.

Test method TestMvcContribUnitTests.Tests.Controllers.HomeControllerTest.IndexTestViewResult threw exception:
System.InvalidOperationException: Unable to create a factory. Be sure a mocking framework is available.


To avoid this error and get the test to pass, add also a reference to a mocking framework. By glancing into the code at the Mvc Contrib Codeplex page, I see that Rhino Mocks and Moq is the two mocking frameworks with support for TestHelper in MvcContrib. Since I have worked most with Moq, I just added a reference to Moq.dll (copy a reference to the Moq.dll assembly file, copy local set to true), and then the test passed.

This now, shows how to get a HttpContext which can be adjusted (Request, Response, Cache and so on), using Moq and MvcContrib TestHelper. The developer can then focus on the task at hand when it comes to unit test, namely to write efficiently unit tests that are relevant and not reinvent the wheel to get HttpContext up and running in Moq, without using MvcContrib TestHelper.

I will look into additional adventures of using MvcContrib TestHelper. If there are other use-case scenarios that should be presented, it would be nice to know.



Testing a RedirectToAction inside a mvc controller action


To test the redirection of an action inside a mvc controller action, use the
following:

Suppose we have return the following:


..
return RedirectToAction("About");
..


The test can then verify that we are returning a RedirectToActionResult and that
the controller action's name is "About" as follows:


..
result.AssertActionRedirect().ToAction("About");
..


In case we return a RedirectToActionResult with overload:


..
return RedirectToAction("About", "Home");
..


To test this we can write:


..
result.AssertActionRedirect().ToController("Home").ToAction("About");
..


Testing the routing in an MVC application


To test out the routes in your MVC application, the following unit test is served as an example using MvcContrib TestHelper:



[TestMethod]
public void RouteTestHomeIndex()
{
var routeTable = RouteTable.Routes;
routeTable.Clear();
routeTable.MapRoute(null, "{controller}/{action}/{id}",
new { controller= "Home", action= "Index",
id = UrlParameter.Optional });

"~/Home/Index".ShouldMapTo(action => action.Index());
"~/Home/About".ShouldMapTo(action => action.About());
}



First line of the unit tests grabs a reference to the RouteTable.Routes property, defined in the System.Web.Routing namespace. The routetable is then cleared and then
the MapRoute method adds a route definition to test. We set up the default route using the MapRoute method. Then we test the two routes "~/Home/Index" and "~/Home/About". These two values are strings and MvcContrib TestHelper has extension method which will check that the route specified in the string maps to a controller using the ShouldMapTo extension method with a generic argument specifying the controller, and in the parameter the action method of the contorller is specifed. Actually, controller => controller.Index is probably a better lambda argument here.

This shows that testing out routes are simple using MvcContrib TestHelper. Just use the string extension methods to test out which controller and which method a certain route should take.

5 comments:

  1. Excellent explanation of using the TestHelper from MvcContrib here:

    http://blog.stevehorn.cc/blog/aspnet-mvc-intro-to-mvccontrib.html

    ReplyDelete
  2. Looks like Google blogger is not treating Generics written in html nicely again. The WithViewData is an extension method that epxects a generic argument of the strongly typed class which the view is typed with, i.e. the model. So you must write WithViewDatA(OfType Bird) where this should be written like WithViewData "LESS than" Bird "Greater than", sorry for the inconvenience, but Google Blogger is misinterpreting generic arguments.. Use WithViewData to check the type of the model which is set with the model keyword at the top in MVC 3 Razor views, if you use this view engine..

    ReplyDelete
  3. The ShouldMapTo extension method expects a generic type argument just like the WithViewData extension method in MvcContrib TestHelper namespace. Google blogger cant interpret generic arguments correctly as it thinks this is HTML... Read ShouldMapTo "LESS Than" HomeController "Greater than" ..

    ReplyDelete
  4. Seriously handy software after the hours of time I've wasted trying to mock http stuff myself!

    ReplyDelete
  5. HttpContext.Current.Session["ValidateBy"] this kind of code is used in .cs file. I've used the solution provided by you. It works when we use Session["ValidateBy"] but not in the code mentioned at the beginning of my comment.

    Can you suggest some solution to this.

    - Harshul

    ReplyDelete