Unit Testing The MVC JsonResult

One of my favorite features of ASP.NET MVC is the support for JSON.  In MVC, an Action Method in a Controller can return a JsonResult.  This really comes in handy when integrating with jQuery to provide AJAX-like functionality in an application.

In this post, I am going to share some of the different ways that I have used for testing Action Methods that return a JsonResult.  Some implementations can be very easy to test, and some can be difficult.  We will start with the easy ones first.

Let’s say that we have the following Action Methods in a Controller that return JSON.  Of course, your methods would likely return something that is calculated, but these are hard-coded for the example:

    1: public ActionResult JsonOne()
    2: {
    3:     string fooString = "test";
    4:     return Json(fooString);
    5: }
    6:  
    7: public ActionResult JsonTwo()
    8: {
    9:     int fooInt = 10;
   10:     return Json(fooInt);
   11: }
   12:  
   13: public ActionResult JsonThree()
   14: {
   15:     List<int> fooList = new List<int>();
   16:     fooList.Add(3);
   17:     fooList.Add(5);
   18:     fooList.Add(7);
   19:  
   20:     return Json(fooList);
   21: }

Unit testing the above implementations is very simple.  We can just write the following tests against the JsonResult.Data property:

    1: [TestMethod]
    2: public void JsonOneTest()
    3: {
    4:     HomeController controller = new HomeController();
    5:     JsonResult actual = controller.JsonOne() as JsonResult;
    6:     
    7:     Assert.AreEqual("test", actual.Data);
    8: }
    9:  
   10: [TestMethod]
   11: public void JsonTwoTest()
   12: {
   13:     HomeController controller = new HomeController();
   14:     JsonResult actual = controller.JsonTwo() as JsonResult;
   15:     
   16:     Assert.AreEqual(10, actual.Data);
   17: }
   18:  
   19: [TestMethod]
   20: public void JsonThreeTest()
   21: {
   22:     HomeController controller = new HomeController();
   23:     JsonResult actual = controller.JsonThree() as JsonResult;
   24:     List<int> result = actual.Data as List<int>;
   25:  
   26:     Assert.AreEqual(3, result.Count);
   27:     Assert.AreEqual(9, result[0]);
   28:     Assert.AreEqual(4, result[1]);
   29:     Assert.AreEqual(7, result[2]);
   30: }

You might notice that in the third test above, we have to convert the JsonResult.Data property to a List<int>.  The Data property on JsonResult is an object, so for some more complex types, we will have to explicitly convert the result to the type that we expect it to be.

This simple testing method works great for regular types, but what if we return a JSON object that contains an anonymous type reference or an IQueryable?  An example of this is shown below:

    1: public ActionResult JsonFour()
    2: {
    3:     var fooAnon = new
    4:     {
    5:         First = "Bob",
    6:         Last = "Smith"
    7:     };
    8:  
    9:     return Json(fooAnon);
   10: }
   11:  
   12: public ActionResult JsonFive()
   13: {
   14:     List<int> fooList = new List<int>();
   15:     fooList.Add(9);
   16:     fooList.Add(4);
   17:     fooList.Add(7);
   18:  
   19:     IQueryable<int> fooQueryable = fooList.AsQueryable()
   20:         .OrderBy(x => x);
   21:  
   22:     return Json(fooQueryable);
   23: }

These JsonResults cannot be tested in the same simple way as the first examples.  So how can we test them?  I will show you the solution that I came up with (there might be a better way to do this, but this is what I came up with).  In the case of JsonFour (line 1), once the anonymous type is returned, we cannot access it from code since anonymous types are limited to scope.  And in the case of JsonFive (line 12), we are returning an IQueryable<int> that will not actually be executed until the enumeration on the query (because of lazy loading). 

The following code shows the method that I use to test these types of JsonResults:

    1: [TestMethod]
    2: public void JsonFourTest()
    3: {
    4:     HomeController controller = new HomeController();
    5:     JsonResult actual = controller.JsonFour() as JsonResult;
    6:     
    7:     var result = JsonHelper.GetJsonObjectRepresentation<IDictionary<string, object>>(actual);
    8:  
    9:     Assert.AreEqual("Bob", result["First"]);
   10:     Assert.AreEqual("Smith", result["Last"]);
   11: }
   12:  
   13: [TestMethod]
   14: public void JsonFiveTest()
   15: {
   16:     HomeController controller = new HomeController();
   17:     JsonResult actual = controller.JsonFive() as JsonResult;
   18:     
   19:     var result = JsonHelper.GetJsonObjectRepresentation<List<int>>(actual);
   20:  
   21:     Assert.AreEqual(3, result.Count());
   22:     Assert.AreEqual(4, result[0]);
   23:     Assert.AreEqual(7, result[1]);
   24:     Assert.AreEqual(9, result[2]);
   25: }
   26:  
   27: //using Rhino.Mocks;
   28: public static T GetJsonObjectRepresentation<T>(JsonResult jsonResult)
   29: {
   30:     var controllerContextMock = MockRepository.GenerateStub<ControllerContext>();
   31:     var httpContextMock = MockRepository.GenerateStub<HttpContextBase>();
   32:     var httpResponseMock = MockRepository.GenerateStub<HttpResponseBase>();
   33:  
   34:     httpContextMock.Stub(x => x.Response).Return(httpResponseMock);
   35:     controllerContextMock.HttpContext = httpContextMock;
   36:  
   37:     jsonResult.ExecuteResult(controllerContextMock);
   38:  
   39:     var args = httpResponseMock.GetArgumentsForCallsMadeOn(x => x.Write(null));
   40:  
   41:     JavaScriptSerializer serializer = new JavaScriptSerializer();
   42:     return serializer.Deserialize<T>(args[0][0] as string);
   43: }

The helper method (line 28) uses RhinoMocks to mock a controller context that can be used to simulate the execution of the JsonResult.  We can then deserialize the JSON string representation back into the object type that was used to create the JSON result.  After doing that, we can compare the actual result to the expected result.

One interesting thing to notice here is what happens in JsonFourTest on line 7.  When we get the deserialized anonymous type, it is returned as a IDictionary<string, object> by the JavaScriptSerializer (line 41 and 42).  Knowing this helps us to cast the result to an IDictionary for accessing the members for testing.  So if you had a list of anonymous types, you can call the helper method using the type List<IDictionary<string, object>> to access the anonymous type members.

If anyone knows of a better way to do this, please let me know.  This is just my quick attempt at solving the problem.