ASP.NET MVC – Unit Testing JsonResult Returning Anonymous Types

by ashic 25. May 2010 15:41

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.

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

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET | ASP.NET MVC

Comments

5/22/2010 10:57:24 PM #

trackback

ASP.NET MVC - Unit Testing JsonResult Returning Anonymous Types

You've been kicked (a good thing) - Trackback from DotNetKicks.com

DotNetKicks.com

5/22/2010 11:32:14 PM #

trackback

ASP.NET MVC – Unit Testing JsonResult Returning Anonymous Types

In this article, Ashic shows a few approaches to unit testing JsonResults that wrap anonymous types in

Community Blogs

5/23/2010 12:42:47 AM #

trackback

Heartysoft.com | ASP.NET MVC – Unit Testing JsonResult Returning Anonymous Types

Thank you for submitting this cool story - Trackback from DotNetShoutout

DotNetShoutout

5/23/2010 10:34:12 AM #

Denis

Клевая статья. (Great article.)

Денис (Denis)

Denis Russia

5/25/2010 10:09:45 AM #

Haacked

I think your fourth approach is the best. Smile

The real issue here is not that you want to unit test the JsonResult. *WE* have tests for the JsonResult. The test you *want* to write is that you put the right data in the result.

This goes into the whole concept of separation of concerns. You shouldn't be concerned with how the object will get serialized when writing that action method. You are concerned with making sure you give the result the *right* data.

As you pointed out, your focus is testing *your* code. That's why I like the dynamic approach. It's the cleanest and quickest way to verify that the data you have the result is the right data and it's less confusing to the next person who has to read and maintain the test. Well done!

Haacked United States

5/25/2010 10:12:15 AM #

Haacked

I forgot to mention one point I meant to make. The title of your post references "Unit testing JsonResult". I meant to point out that this technique doesn't really have anything to do with JsonResult. It more generally applies to any situation where you need to verify the properties of an anonymous object, which makes it have much broader usage implications.

Haacked United States

5/25/2010 10:42:01 AM #

ashic

Phil, I agree the fourth approach is the cleanest and "purest". However, the first two approaches have one significant benefit. They let you test the "whole darn thing" without 5 or 10 or 15 Asserts. Now, there are certainly cases when you'd want 5 or 10 or 15 Asserts, but sometimes you might not. It might seem more of an integration test than a unit test, but to kind of repeat your words - JsonResult works, it's not "my" code, it's tested "vendor" code. As such, it's no different testing the actual output than it is to test the data going into the JsonResult. Those two approaches basically provide a convenient way to test the whole data without all those individual asserts. Another positive is that we "might" have been expecting some incorrect Json on the client side. Since JsonResult works correctly, it will immediately tell us if our expectations on the client side is wrong. Granted that's not what a unit test is for, but it is a nice side effect to have. I guess it's like performing a String.Format on 15 strings and then testing it all in one go rather than testing each of the 15 strings.

And yes, approaches 3 and 4 really point out how to unit test any method returning objects that are instances of / hold instances of anonymous types. The first two approaches, however, don't.

ashic United Kingdom

7/24/2010 9:43:30 PM #

trackback

ASP.NET MVC: Using dynamic type to test controller actions returning JsonResult

I wrote unit tests for my ASP.NET MVC application that uses some jQuery AJAX-components. These components

Community Blogs

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading



Powered by BlogEngine.NET 1.6.1.0
Theme by Ashic Mahtab

Need an expert?

Ashic Mahtab
ashic@live.com
(+44) 07879927393

Stats

Featured Ads

 

Donations

I maintain this site and create all content entirely in my own time just to help you guys out. If you find the stuff helpful, or cool or just like what you see, I'd appreciate you chipping in to help out with the hosting costs. It's easy to do so - just click the button below - no amount is too low :)