Gunnar Peipman's ASP.NET blog

ASP.NET, C#, SharePoint, SQL Server and general software development topics.

Sponsors

News

 
 
 
DZone MVB

Links

Social

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 load data from server in JSON format. I needed to write tests for these methods to make sure that correct data is returned to client. In this posting I will show you how to use dynamic type to test JSON-based action results and therefore avoid creating DTO classes.

Example of controller action

To keep the code compact for reading I represent my controller action as one method without any fragmentation.


public JsonResult ListCompanyRepresentatives(int id)
{
    var party = _partyRepository.GetCompanyById(id);
    var names = from n in party.Representatives
                orderby n.ValidTo, n.RepresentativeParty.DisplayName
                select new 
                {
                    Id = n.Id.ToString(),
                    Name = n.RepresentativeParty.DisplayName,
                    Role = n.Role ?? string.Empty,
                    Context = n.Context ?? string.Empty,
                    ValidFrom = n.ValidFrom.ToShortDateString(),
                    ValidTo = n.ValidTo.ToShortDateString()
                };

    var rows = names.ToArray();
   
var data = new 
    {
        total = rows.Count(),
        page = 1,
        records = rows.Count(),
        rows = rows
    };

    return Json(data, JsonRequestBehavior.AllowGet);       
}

You see here two anonymous types. One of them is created for party representatives and the other one is data structure for JSON-based result. JSON-based result contains array of party representatives. End the end we have one anonymous type that is hosting array of another anonymous type objects. The question is how to write tests for the end result?

Problems with anonymous types

You can say that we can create one DTO class for type returned as array and the other DTO class (or maybe generic class) for JSON results. This would be easy solution but it may end up with large number of DTO classes. This is why I try to get work done with anonymous types.

Anonymous types are not very good to test because they are declared as internal. As we know types in internal scope are visible only to other classes in same assembly. Test are usually located in separate project and separate assembly and by default they don’t see those anonymous types.

There is one problem more. Take a look at the code in test that reads data from JSON result.


var result = _controller.ListCompanyNames(company.Id) as JsonResult;
var data = result.Data;

Data returns us object. To read properties we need to the type of object. The type is anonymous and we cannot cast to anonymous type. We can use reflection but for me it is not good solution because reflection may cause some overhead in performance and if we have many tests then reflection may slow down test runs.

Solution – using dynamic type

Here is the fragment of test that uses dynamics.


dynamic data = result.Data;
dynamic companyName = data.rows[0];
var name = company.Names[0];

Assert.AreEqual(name.Id.ToString(), companyName.nameId);

When we run tests we get errors that object has no property called rows. Well, that’s because anonymous types are internal. We have to make them visible to our tests library.

Open your ASP.NET MVC application project and find AssemblyInfo.cs from folder called Properties. Open AssemblyInfo.cs and add the following line to the end of this file.


[assembly: InternalsVisibleTo("MyProject.Tests.Unit")]

This line tells .NET Framework that internal types of our ASP.NET MVC project must be visible to our tests project. If you have more than one tests library for your ASP.NET MVC application you can make web application internals visible to these libraries exactly the same way. Now you can run your tests and everything should work fine.

Conclusion

Using anonymous types with JSON results may be painful. For previous versions of .NET there are some other solutions to consider: using DTO-s, string comparison or reflection to test JSON results. In .NET 4.0 we can use dynamic type and avoid possible problems related to string comparison (by example, order of properties changes) and reflection (messy code and possible performance overhead). For us dynamic was excellent solution here. This way you can test also other AJAX-based responses that use anonymous types.

Posted: Jul 24 2010, 03:26 PM by DigiMortal | with 12 comment(s) |
Filed under: , , ,

Comments

Erik said:

If your concern is performance, then a dynamic-based approach will not buy you much more than a reflection-based approach, since behind the scenes 'dynamic' calls translate into reflection-based calls.

# July 26, 2010 10:11 AM

DigiMortal said:

I know that but I am very sure that these calls are designed to affect performance as less as possible. When non-expert level programmers try to do something like this then it is possible that they make no such a good decisions. So we affect performance anyway but the question is how much. :)

The other side of the coin is code readability and maintenance. If tests are making heavy use of reflection then they are not so readable anymore and even worse is the fact that beginners are not able to update and write them.

# July 26, 2010 11:04 AM

pluggy13 said:

Very useful to know, I previously used reflection for this and that's quite bothersome. Thanks a lot!

# July 28, 2010 4:44 PM

Thanigainathan said:

Thats very nice article. Want to go through the internals of dynamics

# August 30, 2010 12:27 PM

Abiy Hailemichael said:

I think a easier solution for this wold be to use

RouteValueDictionary from System.Web.Routing.

Example:

var actual= new RouteValueDictionary(result.Data);  Assert.AreEqual(<your value>, actual["page"]);

Assert.AreEqual(<your value>, actual["total"]);

Assert.AreEqual(<your value>, actual["records"]);

Assert.AreEqual(<your value>, actual["rows"]);

# April 12, 2011 3:57 PM