Unit testing of ASP.NET MVC JsonResults can be a source of confusion. The problem arises from the fact that an Action Method itself doesn't produce any html / json / string output – it simply returns an Action Result. ASP.NET MVC then calls the ExecuteResult() method on that Action Result. The ExecuteResult() method is what causes output to be written to the Response stream. Let's take the following controller and action method for example:

    
    
public class HomeController : Controller     
{     
    public ActionResult Index()     
    {     
        var people = new List<Person>     
        {     
             new Person{ Name = "Adam", Age=21},     
             new Person{ Name = "Eve", Age=20},     
             new Person{ Name = "Joe", Age=50}     
        };  
    
        return Json(new     
        {     
            People = people,     
            Count = people.Count,     
            Success = true     
        }, JsonRequestBehavior.AllowGet);     
    }     
}     
    

Here's the (very simple) Person class:

    
    
public class Person     
{     
    public string Name { get; set; }     
    public int Age { get; set; }     
}     
    

As you can see, the Index method is simply returning an anonymous type which holds a list of people, the count, indicates success and wraps the whole thing in a JsonResult. When the Index() action method executes (i.e. is called from the browser), it produces the following json:

{"People":[{"Name":"Adam","Age":21},{"Name":"Eve","Age":20},{"Name":"Joe","Age":50}],"Count":3,"Success":true}

Nothing new there. Now let's say we wish to unit test this method [I know, I know…in a real app, the list of people would come from a service or repository or whatever…but for unit tests, you'd have mock whatevers or stub whatevers and would know what the data being returned would be]. We naively write something like this:

    
    
[TestMethod]     
public void IndexTestWhichShouldFail()     
{     
    //arrange     
    HomeController controller = new HomeController();     
    
    //act     
    var result = controller.Index() as JsonResult;     
    
    //assert     
    Assert.AreEqual(@"{""People"":[{""Name"":""Adam"",""Age"":21},{""Name"":""Eve"",""Age"":20},{""Name"":""Joe"",""Age"":50}],""Count"":3,""Success"":true}",     
        result.Data.ToString());     
}     
    

We run the test, and it fails miserably:

Assert.AreEqual failed. Expected:<{"People":[{"Name":"Adam","Age":21},{"Name":"Eve","Age":20},{"Name":"Joe","Age":50}],"Count":3,"Success":true}>. Actual:<{ People = System.Collections.Generic.List`1[MvcApplication1.Models.Person], Count = 3, Success = True }>.

What went wrong? The reason that the test failed was because we were comparing apples to oranges. We expected a json string of results, but we were passing the ToString() value of the JsonResult's Data property. But shouldn't Data.ToString() simply give us the json string? Erm…no. The Data property simply holds the object passed into the Json() method in Index(). As such, it's simply an instance of an anonymous type. Calling ToString() on it does not cause serialization to json…it simply tells .NET to convert it to a string. The Actual value of the test result above shows what that looks like (i.e. instead of the People list getting serialzed to json, we get an ugly List`1 of Person). So how come it gives us proper json when running from a browser? The reason is that ASP.NET MVC takes the JsonResult and calls its ExecuteResult() method, which (among other things) writes the json to the Response.

So how do we unit test that?

Well, if we were returning non-anonymous types, then we could simply cast JsonResult.Data to the expected type and check its properties. But we're returning an anonymous type, aren't we?

So how do we unit test that?

Well, there're three approaches we can take.

Approach 1: The Easy Way

We know that we have the object and we know what the json for it should be. We can just serialize the object and see if it matches what we expect. Here's the test:

    
    
[TestMethod()]     
public void IndexTest()     
{     
    //arrange     
    HomeController controller = new HomeController();  
    
    //act     
    var result = controller.Index() as JsonResult;     
    var serializer = new JavaScriptSerializer();     
    var output = serializer.Serialize(result.Data);     
    
    //assert     
    Assert.AreEqual(@"{""People"":[{""Name"":""Adam"",""Age"":21},{""Name"":""Eve"",""Age"":20},{""Name"":""Joe"",""Age"":50}],""Count"":3,""Success"":true}",     
        output);     
}     
    

Nothing complicated there. It's basically doing the same thing as the ExecuteResult() method of JsonResult does (in terms of producing the Json string via the JavaScriptSerializer). Now some of you may argue that this is not a unit test as it tests serialization as well as the method under test (i.e. Index). I would disagree. The reason being that you should only be testing your code. Testing JavaScriptSerializer should not be your focus when you're testing the Index method. And since checking the serialzed result is easier for you (i.e. you know what the json should be), you may as well check against that.

Approach 2: The "Pure" Way

This approach executes pretty much what the MVC framework does to produce output. For this, we're going to use Moq to fake a controller context and response and capture the content written to Response during execution in a StringBuilder. We will execute the result with the fake context, which will fill our StringBuilder with the serialized output. We will then check to see if it is what we want it to be. Here's the test:

    
    
[TestMethod()]     
public void IndexTestWithMock()     
{     
    //arrange     
    HomeController controller = new HomeController();     
    var result = controller.Index();     
    var sb = new StringBuilder();     
    Mock<HttpResponseBase> response = new Mock<HttpResponseBase>();     
    response.Setup(x => x.Write(It.IsAny<string>())).Callback<string>(y =>     
    {     
        sb.Append(y);     
    });     
    Mock<ControllerContext> controllerContext = new Mock<ControllerContext>();     
    controllerContext.Setup(x => x.HttpContext.Response).Returns(response.Object);     
    
    //act     
    result.ExecuteResult(controllerContext.Object);     
    
    //assert     
    Assert.AreEqual(@"{""People"":[{""Name"":""Adam"",""Age"":21},{""Name"":""Eve"",""Age"":20},{""Name"":""Joe"",""Age"":50}],""Count"":3,""Success"":true}",     
        sb.ToString());     
}     
    

Notice that our "act" of the test is not when we call controller.Index(), rather when we call ExecuteResult() on the JsonResult. In the previous approach, we simply called into the controller's action method. In this approach, we are actually doing what MVC does when dealing with a Request (at least, parts of it). To make it play the way we want it to, we created Mocks that behave the way we want them to. [Of course, the "arrange", "act" and "assert" parts are just nomenclature and one could say that the "act" is when Index() is called and everything afterwards is part of "assert"…]

Approach 3: Reflection

The previous two approaches compared the generated json against the expected json. As I said, that might upset some people as it kind of tests serialization too. I'm fine with it, but if someone wanted to test the JsonResult itself and not the generated output, one approach they can take is reflection. Here's a test that does just that:

    
    
[TestMethod]     
public void IndexTestWithReflection()     
{     
    //arrange     
    HomeController controller = new HomeController();     
    
    //act     
    var result = controller.Index() as JsonResult;     
    
    //assert     
    var data = result.Data;     
    var type = data.GetType();     
    var countPropertyInfo = type.GetProperty("Count");     
    var expectedCount = countPropertyInfo.GetValue(data, null);     
    
    Assert.AreEqual(3, expectedCount);     
}     
    

Since result.Data is an instance of an anonymous type, you can't really cast it to anything useful. You can check the values of result.Data via reflection. It can be a bit messy, but you could write a utility library to get to the properties in an easier fashion. There is one problem though…you're checking for strings (i.e. the property "Count") and as such, refactorings like renaming of a property will cause your tests to fail.

Approach 4: Dynamic

One way to avoid reflection would be to use .Net 4.0's dynamic capabilities. This does, however, require a bit of tinkering. We'll need to open up AssemblyInfo.cs of the MVC application and add this to the bottom:

    
[assembly:InternalsVisibleTo("TestProject1")]     

Why we need to do this is explained in this article.

Once that's done, we can write our test like this:

    
    
[TestMethod]     
public void IndexTestWithDynamic()     
{     
    //arrange     
    HomeController controller = new HomeController();     
    
    //act     
    var result = controller.Index() as JsonResult;     
    
    //assert     
    dynamic data = result.Data;  
    
    Assert.AreEqual(3, data.Count);     
    Assert.IsTrue(data.Success);     
    Assert.AreEqual("Adam", data.People[0].Name);     
}     
    

Note how we're avoiding reflection, but still are testing the data that went into the JsonResult as opposed to the json string gotten from executing the JsonResult.


All three of these approaches will work. Pick whichever one suits your ideologies.

And yes, I'm aware that the Index() method would be better off returning a PagedList<Person>, and that Success=true is for the most part redundant (if there was an error, it should throw an exception and the calling javascript code should be able to understand that there was an error). The Index() method is for illustrative purposes only ;)

Update

I've added a fourth approach (i.e. dynamic). The source code download below contains the update.

Code

Click here to download the source code.

Shout it kick it on DotNetKicks.com

---------------------------------------------------------

Questions  and comments relating to this article are welcome. Comments completely unrelated to the article and posted with the sole intention of putting your link here are not.

If you spam, your comment will not be approved, will be deleted and your IP blocked. I maintain my site almost daily and such comments – even if they pass the spam filter – will get removed as soon as possible. If this gets too tedious, I may disable comments entirely. Please don't ruin it for everybody else.

---------------------------------------------------------