次の方法で共有


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.

Comments

  • Anonymous
    February 24, 2009
    PingBack from http://www.anith.com/?p=13384

  • Anonymous
    February 24, 2009
    Thank you for submitting this cool story - Trackback from DotNetShoutout

  • Anonymous
    February 24, 2009
    Intersting post! If you use the testhelpers from MVCContrib you could also test using the following syntax:  var json = controller.FindRooms(new RoomRequirements { From = from, To = to })                .AssertResultIs<JsonResult>()                .AssertPayloadIs<IList<Room>>();            Assert.That(json.Count, Is.EqualTo(2));            Assert.That(json[0].Number, Is.EqualTo("201"));            Assert.That(json[0].Type, Is.EqualTo("Single"));            Assert.That(json[0].Price, Is.EqualTo(1100.0));

  • Anonymous
    September 17, 2009
    I found another way to achieve this...I find it much easier, I use the RouteValueDictionary, I created an extension method to the JsonResult: public static RouteValueDictionary AsRouteValueDictionary(this JsonResult jsonResult)        {            return new RouteValueDictionary(jsonResult.Data);        } Then, I can use it this way: var result = jsonResult.AsRouteValueDictionary(); Assert.That(result["Status"], Is.True);

  • Anonymous
    March 18, 2010
    You can just use .ToString() on the anonymous objects and then compare the string values.

  • Anonymous
    July 30, 2010
    Thanks for giving this interesting information on using Unit testing to check our code. I request you to give more detailed information on unit testing tools also so that we can more knowledge on this.

  • Anonymous
    November 26, 2010
    One of the best on this topic. This one rocks too kbochevski.blogspot.com/.../unit-testing-mvcnet.html. There is testing json result too in it and many things more - like the datacontext unit testing which is done in a very cool manner.

  • Anonymous
    January 18, 2012
    After upgrading to MVC3 & VS2010, GetJsonObjectRepresentation<T> no longer works. The ParentActionViewContext on the mocked ControllerContext is null. Any ideas?

  • Anonymous
    January 18, 2012
    Update: The ParentActionViewContext isn't null per se, instead it is throwing a System.NullReferenceError

  • Anonymous
    September 05, 2012
    Why do you involve the httpcontext class? Why not directly test the JsonResult serializer/deserializer?

  • Anonymous
    March 30, 2015
    easier way to handle JsonFour:   www.gibedigital.com/.../unit-testing-aspnet-mvc-jsonresult