How to unit test an Action method which returns JsonResult?
Asked Answered
T

9

49

If I have a controller like this:

[HttpPost]
public JsonResult FindStuff(string query) 
{
   var results = _repo.GetStuff(query);
   var jsonResult = results.Select(x => new
   {
      id = x.Id,
      name = x.Foo,
      type = x.Bar
   }).ToList();

   return Json(jsonResult);
}

Basically, I grab stuff from my repository, then project it into a List<T> of anonymous types.

How can I unit-test it?

System.Web.Mvc.JsonResult has a property called Data, but it's of type object, as we expected.

So does that mean if I want to test that the JSON object has the properties I expect ("id", "name", "type"), I have to use reflection?

EDIT:

Here's my test:

// Arrange.
const string autoCompleteQuery = "soho";

// Act.
var actionResult = _controller.FindLocations(autoCompleteQuery);

// Assert.
Assert.IsNotNull(actionResult, "No ActionResult returned from action method.");
dynamic jsonCollection = actionResult.Data;
foreach (dynamic json in jsonCollection)
{
   Assert.IsNotNull(json.id, 
       "JSON record does not contain \"id\" required property.");
   Assert.IsNotNull(json.name, 
       "JSON record does not contain \"name\" required property.");
   Assert.IsNotNull(json.type, 
       "JSON record does not contain \"type\" required property.");
}

But I get a runtime error in the loop, stating "object does not contain a definition for id".

When I breakpoint, actionResult.Data is defined as a List<T> of anonymous types, so I figure if I enumerate through these, I can check the properties. Inside the loop, the object does have a property called "id" - so not sure what the issue is.

Timelag answered 14/2, 2011 at 6:36 Comment(10)
Re the edit - you could try something like var items = (IEnumerable)actionResult.Data; foreach(dynamic obj in items) {...}Schaffner
I've tested here with ` var list = (IList)data; Assert.AreEqual(list.Count, 2); dynamic obj = data[0]; Assert.AreEqual(obj.id, 12); Assert.AreEqual(obj.name, "Fred"); Assert.AreEqual(obj.type, 'a'); obj = data[1]; Assert.AreEqual(obj.id,14); Assert.AreEqual(obj.name, "Jim"); Assert.AreEqual(obj.type, 'c'); foreach (dynamic d in list) { if (d.id == null) throw new InvalidOperationException(); }` and it seemed fine...Schaffner
let me try that code tomorrow when i get in the office. cheers.Timelag
@Marc Gravell - are you sure you tried it? I copied and pasted that code above exactly, and it didn't work. Firstly, data is an object, so it doesn't have an indexer (data[0] gives syntax error). correct me if im wrong, but all that dynamic is doing is giving late binding to the type, all we're doing here is casting an object to an IList<object>, so when we enumerate over it, it's still an object. i think i'm going to have to de-serialize the object.Timelag
@rpm1984 please note the cast to IListSchaffner
@Marc - i did cast to IList. But as i said above, it's a collection of object, so the error remains.Timelag
@Timelag when fetched into obj, obj is dynamic - so the late binding per item still occurs.Schaffner
@Marc Gravell - okay well i haven't used dynamic too much, but as i've said - i can't get your solution to work, neither could @Matt Greer. So i've gone with his reflection-based approach.Timelag
@RPM - not a problem; I'm just glad you got a working solutionSchaffner
@Marc - appreciate your help as well. :)Timelag
M
21

RPM, you look to be correct. I still have much to learn about dynamic and I cannot get Marc's approach to work either. So here is how I was doing it before. You may find it helpful. I just wrote a simple extension method:

    public static object GetReflectedProperty(this object obj, string propertyName)
    {  
        obj.ThrowIfNull("obj");
        propertyName.ThrowIfNull("propertyName");

        PropertyInfo property = obj.GetType().GetProperty(propertyName);

        if (property == null)
        {
            return null;
        }

        return property.GetValue(obj, null);
    }

Then I just use that to do assertions on my Json data:

        JsonResult result = controller.MyAction(...);
                    ...
        Assert.That(result.Data, Is.Not.Null, "There should be some data for the JsonResult");
        Assert.That(result.Data.GetReflectedProperty("page"), Is.EqualTo(page));
Macleod answered 16/2, 2011 at 3:5 Comment(4)
Glad i'm not alone on trying to get Marc's solution to work. This works great. I'll delete my answer and accept this since it's definitely the better approach. Thanks!Timelag
For the record a dynamic solution would work. It'd basically be the same as this just with prettier syntax. Internally it'd still be using reflection. Much like how ViewBag is just ViewData with prettier syntax. I just haven't looked into dynamic enough to implement it yet. I will when I have a bit of downtime. I know it involves implementing IDynamicObject and wrapping JsonResult.Data with it.Macleod
Where does the ThrowIfNull method come from? Is there a class you're extending or am I missing something?Betti
That's just a little extension method of mine. It throws an ArgumentNullException if it's null or returns the object.Macleod
C
58

I know I'm a bit late on this guys, but I found out why the dynamic solution wasn't working:

JsonResult returns an anonymous object and these are, by default, internal, so they need to be made visible to the tests project.

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")]

Quoted from: http://weblogs.asp.net/gunnarpeipman/archive/2010/07/24/asp-net-mvc-using-dynamic-type-to-test-controller-actions-returning-jsonresult.aspx

I thought it would be nice to have this one for the record. Works like a charm

Crystlecs answered 24/3, 2011 at 22:11 Comment(0)
M
21

RPM, you look to be correct. I still have much to learn about dynamic and I cannot get Marc's approach to work either. So here is how I was doing it before. You may find it helpful. I just wrote a simple extension method:

    public static object GetReflectedProperty(this object obj, string propertyName)
    {  
        obj.ThrowIfNull("obj");
        propertyName.ThrowIfNull("propertyName");

        PropertyInfo property = obj.GetType().GetProperty(propertyName);

        if (property == null)
        {
            return null;
        }

        return property.GetValue(obj, null);
    }

Then I just use that to do assertions on my Json data:

        JsonResult result = controller.MyAction(...);
                    ...
        Assert.That(result.Data, Is.Not.Null, "There should be some data for the JsonResult");
        Assert.That(result.Data.GetReflectedProperty("page"), Is.EqualTo(page));
Macleod answered 16/2, 2011 at 3:5 Comment(4)
Glad i'm not alone on trying to get Marc's solution to work. This works great. I'll delete my answer and accept this since it's definitely the better approach. Thanks!Timelag
For the record a dynamic solution would work. It'd basically be the same as this just with prettier syntax. Internally it'd still be using reflection. Much like how ViewBag is just ViewData with prettier syntax. I just haven't looked into dynamic enough to implement it yet. I will when I have a bit of downtime. I know it involves implementing IDynamicObject and wrapping JsonResult.Data with it.Macleod
Where does the ThrowIfNull method come from? Is there a class you're extending or am I missing something?Betti
That's just a little extension method of mine. It throws an ArgumentNullException if it's null or returns the object.Macleod
M
10

I'm a bit late to the party, but I created a little wrapper that lets me then use dynamic properties. As of this answer I've got this working on ASP.NET Core 1.0 RC2, but I believe if you replace resultObject.Value with resultObject.Data it should work for non-core versions.

public class JsonResultDynamicWrapper : DynamicObject
{
    private readonly object _resultObject;

    public JsonResultDynamicWrapper([NotNull] JsonResult resultObject)
    {
        if (resultObject == null) throw new ArgumentNullException(nameof(resultObject));
        _resultObject = resultObject.Value;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (string.IsNullOrEmpty(binder.Name))
        {
            result = null;
            return false;
        }

        PropertyInfo property = _resultObject.GetType().GetProperty(binder.Name);

        if (property == null)
        {
            result = null;
            return false;
        }

        result = property.GetValue(_resultObject, null);
        return true;
    }
}

Usage, assuming the following controller:

public class FooController : Controller
{
    public IActionResult Get()
    {
        return Json(new {Bar = "Bar", Baz = "Baz"});
    }
}

The test (xUnit):

// Arrange
var controller = new FoosController();

// Act
var result = await controller.Get();

// Assert
var resultObject = Assert.IsType<JsonResult>(result);
dynamic resultData = new JsonResultDynamicWrapper(resultObject);
Assert.Equal("Bar", resultData.Bar);
Assert.Equal("Baz", resultData.Baz);
Mona answered 13/6, 2016 at 9:20 Comment(0)
Z
3

Here's one I use, perhaps it is of use to anyone. It tests an action that returns a JSON object for use in clientside functionality. It uses Moq and FluentAssertions.

[TestMethod]
public void GetActivationcode_Should_Return_JSON_With_Filled_Model()
{
    // Arrange...
    ActivatiecodeController activatiecodeController = this.ActivatiecodeControllerFactory();
    CodeModel model = new CodeModel { Activation = "XYZZY", Lifespan = 10000 };
    this.deviceActivatieModelBuilder.Setup(x => x.GenereerNieuweActivatiecode()).Returns(model);

    // Act...
    var result = activatiecodeController.GetActivationcode() as JsonResult;

    // Assert...
    ((CodeModel)result.Data).Activation.Should().Be("XYZZY");
    ((CodeModel)result.Data).Lifespan.Should().Be(10000);
}
Zlatoust answered 9/11, 2015 at 7:38 Comment(1)
Casting the result as JsonResult finally helped me access the Value property with aspnetcore.mvc v2.0.1.Downrange
C
1

I extend the solution from Matt Greer and come up with this small extension:

    public static JsonResult IsJson(this ActionResult result)
    {
        Assert.IsInstanceOf<JsonResult>(result);
        return (JsonResult) result;
    }

    public static JsonResult WithModel(this JsonResult result, object model)
    {
        var props = model.GetType().GetProperties();
        foreach (var prop in props)
        {
            var mv = model.GetReflectedProperty(prop.Name);
            var expected = result.Data.GetReflectedProperty(prop.Name);
            Assert.AreEqual(expected, mv);
        }
        return result;
    }

And i just run the unittest as this: - Set the expected data result:

        var expected = new
        {
            Success = false,
            Message = "Name is required"
        };

- Assert the result:

        // Assert
        result.IsJson().WithModel(expected);
Cavatina answered 23/7, 2014 at 8:26 Comment(0)
S
1

My solution is to write the extension method:

using System.Reflection;
using System.Web.Mvc;

namespace Tests.Extensions
{
    public static class JsonExtensions
    {
        public static object GetPropertyValue(this JsonResult json, string propertyName)
        {
            return json.Data.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public).GetValue(json.Data, null);
        }
    }
}
Shroyer answered 29/2, 2016 at 20:57 Comment(0)
L
0

If in the test you know what exactly the Json data result should be then you can just do something like this:

result.Data.ToString().Should().Be(new { param = value}.ToString());

P.S. This would be if you had used FluentAssertions.Mvc5 - but it shouldn't be hard to convert it to whatever testing tools you use.

Localism answered 30/6, 2017 at 14:5 Comment(0)
K
0

This is how I assert it

foreach (var item in jsonResult.Data as dynamic) {
    ((int)item.Id).ShouldBe( expected Id value );
    ((string)item.name).ShouldBe( "expected name value" );
}
Kelso answered 24/4, 2019 at 9:17 Comment(0)
B
0

You can convert the value property of the JsonResult into an instance of a known type and then simply access it's properties.

public static class JsonResultExtensions
{
    public static T ExtractType<T>(this JsonResult result)
    {
        var resultAsJson = JsonSerializer.Serialize(result.Value);
        return JsonSerializer.Deserialize<T>(resultAsJson);
    }
}

below is an example of the use of the extension method:

MyModel model = jsonResult.ExtractType<MyModel>();
Assert.True(model.Success);
Bacteriophage answered 8/11, 2022 at 13:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.