In ASP.NET MVC 3, Microsoft has made unit testing controller actions quite easy. Generally you follow 3 basic steps:
- setup your dependencies
- call the controller action you want to test
- assert something on the properties(usually the Model property) of the returned ActionResult derived object.
However there still is a pain point when the action under test makes use of so called ASP.NET intrinsic objects (Request, Response, Session, Cache, Server etc.) which are hold in the HttpContext instance.
Mocking the HttpContext, while doable, is not quite straight forward, nor as fast as mocking a simpler object. It simply is an ugly job.
But do not panic! There is a solution which in my opinion is the most elegant of all: Custom Model Binders. They can be use to factor out of the action methods those hard to mock (relatively speaking) intrinsic objects.
Lets look at an example. Suppose I have a custom made identity object that implements some very “special” logic.
public class MyIdentity : IIdentity { public string MyOwnCustomIdentiyPropertyThatSupposedlyINeedSomewhere { get; set; } public string AuthenticationType { get { return "Custom"; } } public bool IsAuthenticated { get { return true; } } public string Name { get { return "Name"; } } }
The following is the controller action that uses this Identity object:
public ActionResult About() { ViewBag.Message = (ControllerContext.HttpContext.User.Identity as MyIdentity).MyOwnCustomIdentiyPropertyThatSupposedlyINeedSomewhere; return View(); }
Testing this action method will be impossible unless a mock for HttpContext is supplied which we try to avoid.
Wouldn’t it be nice to have an action method like the following?
public ActionResult Index(MyIdentity identity) { ViewBag.Message = identity.MyOwnCustomIdentiyPropertyThatSupposedlyINeedSomewhere; return View(); }
Well it would be very nice and we can. This is possible by the help of a custom model binder for the MyIdentity type that will be registered on application startup. Code shown bellow:
public class MyIdentityModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { return controllerContext.HttpContext.User.Identity; } } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); //register the new binder ModelBinders.Binders.Add(typeof(MyIdentity), new MyIdentityModelBinder()); }
Now I can write a simple unit test for this functionality like this:
[TestMethod] public void Index_ShoudlDisplayCorrectMessageFromIdentity() { //arrange var dummyIdentity = new MyIdentity(); dummyIdentity.MyOwnCustomIdentiyPropertyThatSupposedlyINeedSomewhere = "test"; var sut = new HomeController(); //act ViewResult result = sut.Index(dummyIdentity) as ViewResult; //assert Assert.AreEqual(result.ViewBag.Message, "test"); }
The same trick can be applied for any of the intrinsic objects or their properties. You just need to create other custom binders.
And this is it, hope this will make your life easier when creating tests for the controller.