Unit test a method that returns JsonResult in WebApi

While you can return any data type from a webapi action method, JsonResult is one of the most convenient return type because of flexibility it provides. You just need to pass an object to Json function and it'll generate a valid Json response for your data. When writing unit tests in such cases, you may feel the need to deserialize the Json response to extract it's values. In this article, I'll just provide you steps to write unit tests for JsonResult. I'll not get into the details regarding creating webapi and test projects. You can read the already written articles here and here. I'd rather jump straight to the problem and the solution.

The Controller & Action Methods

Suppose this is the action method that returns some products. A simple product class looks like this. For the sake of brevity, I've created just one property called Name.

public class Product
{
   public string Name {get; set;}
}

And this is the how the controller and action methods look

public class ProductController: ApiController
{public IHttpActionResult GetProduct(int id){	var product = GetSampleProducts().FirstOrDefault(x => x.Id == id);	return Json(new {		Success = true,		Name = product.Name,		Id = product.Id				});}public IHttpActionResult GetProducts(){	var products = GetSampleProducts();	return Json(new {		Success = true,		Products = products	});}
private Products[] GetSampleProducts(){	var productList = new List<Product>();	productList.Add(new Product(){		Id = 1,		Name = "Television";	});	productList.Add(new Product(){		Id = 2,		Name = "Mobile Phone";	});	productList.Add(new Product(){		Id = 3,		Name = "Desktop";	});	productList.Add(new Product(){		Id = 4,		Name = "Laptop	});	return productList.ToArray();}
}

Needless to say, in a real world application, the product data will be fetched from some data provider source such as a database, a web service etc. Now to test this action, let's write a test. As I said, I won't go through the process of setting up the test project. You can go through the above links to do that. Inside your test file, create following two methods.

public static T GetValueFromJsonResult<T>(JsonResult<object> jsonResult, string propertyName)
{var allProperties = jsonResult.Content.GetType().GetProperties();var property = allProperties.FirstOrDefault(p => string.CompareOrdinal(p.Name, propertyName) == 0);
if (null == property)	throw new ArgumentException("propertyName not found", nameof(propertyName));return (T)property.GetValue(jsonResult.Content, null);
}

public static T GetValueFromActionResult<T>(IHttpActionResult actionResult, string propertyName)
{
   return GetValueFromJsonResult<T>((JsonResult<object>)actionResult, propertyName);
}

The method actually uses reflection to read the content of your json result for a given property and returns the property value if the property is found. If it's not found, it throws an exception. We have just create another method that takes IHttpActionResult as the parameter. The reason is simple and easy way to handle all kinds of responses (Xml, Json or something else may be?). You just need to modify the second method to call appropriate methods for other response types. We for the sake of brevity stick to the JsonResult type. Now let's setup a test

[Test]
public void GetProduct_works(){var productController = new ProductController();var result = productController.GetProduct(1);//parse resultvar success = GetValueFromActionResult<bool>(result, "Success");Assert.IsTrue(true, success);var id = GetValueFromActionResult<int>(result, "Id");Assert.AreEqual(1, id);
}

Note that I'm using NUnit as my unit test framework. However the process is same for any framework for that matter. If you run the test, you'd get a success response from the test results. This way you should be able to test the JsonResult from webapi.

But there is a problem

The default way of parsing JsonResult will not be able to read the Json keys which are below level one. To read the keys beyond level one (i.e. nested json), the following assertions won't work.

[Test]
public void GetProducts_works(){var productController = new ProductController();var result = productController.GetProducts();//parse resultvar success = GetValueFromActionResult<bool>(result, "Success");Assert.IsTrue(true, success);// this worksvar count = GetValueFromActionResult<dynamic>(result, "Products").Count;Assert.AreEqual(4, count); // this fails
}

In the second assertion above, we have queried the Products property of JsonResult as a dynamic object. So inside json, the result looks like

{Success: "true",Products: [	{Id: 1, Name: "Television"},	{Id: 2, Name: "Mobile Phone"},	{Id: 3, Name: "Desktop"},	{Id: 4, Name: "Laptop"}]
}

Because by default, the nested keys of dynamic object are internal, we won't be able to access the Count property of the Products. The result, the test fails even if the function worked correctly.

Solution

The solution involves telling our webapi to expose these internal properties to our test projects. This is achieved using InternalsVisibleToAttribute. What this attribute does is that it specifies that some other project is a friend to current project and hence should be given access to the internal properties. So to make the above test work, open your Global.asax file of your webapi project and add the following above the namespace specification.

[assembly: InternalsVisibleTo("Your.TestProject.NameSpace")]
namespace Your.WebApi.NameSpace
{
.
.
.
}

This way, you are specifying that your test project is a friend to your webapi project and so it's internal properties should be accessible via test project. Now if you run the tests again, they will succeed. Happy TDD