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=13384Anonymous
February 24, 2009
Thank you for submitting this cool story - Trackback from DotNetShoutoutAnonymous
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.NullReferenceErrorAnonymous
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