38

I was trying to unit test a method in one of my Controllers returning a JsonResult. To my surprise the following code didn't work:

[HttpPost]
public JsonResult Test() {
    return Json(new {Id = 123});
}

This is how I test it (also note that the test code resides in another assembly):

// Act
dynamic jsonResult = testController.Test().Data;

// Assert
Assert.AreEqual(123, jsonResult.Id);

The Assert throws an exception:

'object' does not contain a definition for 'Id'

I've since resolved it by using the following:

[HttpPost]
public JsonResult Test() {
   dynamic data = new ExpandoObject();
   data.Id = 123;
   return Json(data);
}

I'm trying to understand why isn't the first one working ? It also seems to be working with basically anything BUT an anonymous type.

Dimitar Dimitrov
  • 14,868
  • 8
  • 51
  • 79
  • 1
    I tried your code with the anonymous type and it worked fine for me. Not sure why you're getting that error. – Kirk Woll Jun 01 '13 at 19:11
  • What do you get when you print out `jsonResult.GetType()`? (the error indicates it thinks it's of type `object` rather than of type `<>f__AnonymousType0`, which is what I'd expect) – Kirk Woll Jun 01 '13 at 19:14
  • The type is object indeed. I expected it to work myself, not sure why I'm getting those results. – Dimitar Dimitrov Jun 01 '13 at 19:32
  • 1
    This short snippet works fine for me: `var result = new JsonResult { Data = new { Id = "foo" } }; dynamic foo = result.Data; Console.WriteLine(foo.Id);` Does it work for you? – Kirk Woll Jun 01 '13 at 19:37
  • @KirkWoll, thanks for your help, mate, It appears that anonymous can't be used in other libraries (check out LukLed's answer), I didn't know this, gonna read some more. – Dimitar Dimitrov Jun 01 '13 at 19:58
  • @KirkWoll and, yes by the way it does work (when I place it in the Unit Tests project), however as soon as I place it in Production and access it from the Unit Test project -> it fails. It was driving me crazy. – Dimitar Dimitrov Jun 01 '13 at 19:58
  • possible duplicate of [How to unit test an Action method which returns JsonResult?](http://stackoverflow.com/questions/4989471/how-to-unit-test-an-action-method-which-returns-jsonresult) – Edward Brey Apr 29 '14 at 16:57

3 Answers3

39

To be clear, the specific problem you are encountering is that C# dynamic does not work with non-public members. This is by design, presumably to discourage that sort of thing. Since as LukLed stated, anonymous types are public only within the same assembly (or to be more precise, anonymous types are simply marked internal, not public), you are running into this barrier.

Probably the cleanest solution would be for you to use InternalsVisibleTo. It allows you to name another assembly that can access its non-public members. Using it for tests is one of the primary reasons for its existance. In your example, you would place in your primary project's AssemblyInfo.cs the following line:

[assembly: InternalsVisibleTo("AssemblyNameOfYourTestProject")]

Once you do that, the error will go away (I just tried it myself).

Alternatively, you could have just used brute force reflection:

Assert.AreEqual(123, jsonResult.GetType().GetProperty("Id").GetValue(jsonResult, null));
Kirk Woll
  • 76,112
  • 22
  • 180
  • 195
  • 1
    Can you explain the last option ? – Ani Nov 24 '14 at 17:12
  • Can you clarify your question? Are you wondering about reflection in general or something about how it applies to this question/answer? – Kirk Woll Nov 24 '14 at 17:14
  • reflection in general – Ani Nov 24 '14 at 17:35
  • 1
    @Ani, in that case, I think that is too big a topic to discuss in comments and such elaboration would, I believe, be distracting from the OP's original question. The essential gist here is that you can get the `PropertyInfo` from a `Type` and using it can get or set the value of the property, regardless of whether or not the type is internal by using the `GetValue` method. – Kirk Woll Nov 24 '14 at 18:37
15

Having read the responses here and then looking further afield I found a 2009 msdn blog post with a different approach again. But.. in the comments was a very simple and very elegant solution by Kieran ... to use .ToString().

In your original case:

[HttpPost]
public JsonResult Test()
{
    return Json(new {Id = 123});
}

You could test by doing:

var jsonResult = controller.Test();
Assert.AreEqual("{Id = 123}", jsonResult.Data.ToString());

I much prefer this solution as it:

  • avoids changing the original code (InternalsVisibleTo, ExpandoObject),
  • avoids using MvcContrib and RhinoMocks (no issue with either of these but why add just to be able to test JsonResult?), and,
  • avoids using Reflection (adds complexity to the tests).
Scotty.NET
  • 12,533
  • 4
  • 42
  • 51
5

Anonymous types are internal, so you can't expose them to another library, the one with tests. If you placed testing code in the same library as controller, it will work.

LukLed
  • 31,452
  • 17
  • 82
  • 107
  • 2
    You could also declare the testing assembly a "friend assembly" in order to expose internals. http://msdn.microsoft.com/en-us/library/0tke9fxk(v=vs.80).aspx – Ben Reich Jun 01 '13 at 22:04