Blog

Using dynamic to unit test an Action Method that returns JsonResult with MVC 3 and C# 4

07-04-2011 by Sergi Papaseit

We’ve all been there, haven’t we? We have an action method that we call through Ajax.ActionLink, Ajax.BeginForm or even with jQuery.ajax, and returning a JsonResult is what makes the most sense. But whatever you pass to a  JsonResult only gets exposed through the JsonResult.Data property which, unfortunately, is of type object.

JsonResult is meant to be serialized into a string with Json notation so it doesn’t matter that Data is of type object, but, how do you access the properties inside the Data object when you want to unit test your view?

 

The setup

We need an action in a controller that returns a JsonResult; it doesn’t matter how it gets called from the view or what we’d do with the result, so let’s keep it simple:

public ActionResult DoSomething()
{
    var ok = Act();

    var message = ok? "All done!" : "Oops!";

    return Json(new { Message = message });
}

And the unit test:

[TestClass]
public class Test
{
    [TestMethod]
    public void DoSomething_Does_Something()
    {
        // Arrange
        var controller = TestFakes.CreateController();
        
        // Act
        var json = controller.DoSomething() as JsonResult;

        // Assert
        Assert.IsNotNull(json.Data.Message); // --> FAIL!
    }
}

The test fails not because Message is null, but because json.Data is of type System.Object and System.Object doesn’t have a Message property, obviously.

What now? Well, I give it away in the title of this post so it’s hardly a cliff-hanger, is it?

 

Dynamic to the rescue

Thanks to the magic of statically typing as dynamic in c# 4 this should be a piece of cake. Let’s modify the test method:

[TestClass]
public class Test
{
    [TestMethod]
    public void DoSomething_Does_Something()
    {
        // Arrange
        var controller = TestFakes.CreateController();
        
        // Act
        var json = controller.DoSomething() as JsonResult;
        
        // Dynamic magic: just assign the Data property
        // to a variable of type dynamic!
        dynamic data = json.Data;
        
        // And get our message out of it.
        string msg = data.Message;

        // Assert
        Assert.AreEqual("All done!", msg); // --> OK!
    }
}

It’s that simple: just assign the Data object to a variable of type dynamic, then access the Message property. Note that I cannot declare the variable msg as var. That’s because data.Message is a dynamic expression that will be resolved at run time, so the compiler wouldn’t know what to substitute var for at compile time.

 

Er, it doesn’t work

If you actually run this test, you’ll get an "object does not contain a definition for Message" error. Oops. Didn’t we convert json.Data to a dynamic object? Well, yes but…

 

Anonymous types are internal

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

 

The solution

This is, fortunately, very very simple to solve.

Assuming you have your test classes in an assembly called MyProject.Tests,  find the file AssemblyInfo.cs in the Properties folder in your MVC project. Open AssemblyInfo.cs and add the following line to the end of the file:

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

 

Do you know of a better way? Any questions? Let me know!

Leave a comment

11 comment(s)

gravatar
DavidS 2011-04-25 21:48

That is going to save me a lot of headaches! Thanks for that. Question is, how did you find out about the solution to the problems you've encountered?

gravatar
Sergi 2011-04-26 12:05

@David - Glad I can help! :)

I was having trouble getting this to work and I actually started searching on StackOverflow (to no avail) and Google. After a while a Google search brought up the "anonymous types are internal" issue. Bit of luck there I guess :)

gravatar
Andrey 2011-05-04 17:00

Thanks. Thats really what I wanted!

gravatar
Sergi 2011-05-04 17:17

@Andrey - You're most welcome =)

gravatar
Adam. 2011-05-16 17:01

Thank you very much! I think the last paragraph should read "find the file AssemblyInfo.cs in the Properties folder in the project to test" rather than "find the file AssemblyInfo.cs in the Properties folder in your tests project" however (wrong way round I think!) - but thank you this was extremely helpful.

gravatar
Sergi 2011-05-16 19:05

@Adam,

Thanks, you're kinda right: it was supposed to say "in your main project". I've fixed it now, thanks again!

gravatar
Eduardo 2011-06-15 18:31

Sergi,

Thanks for explanation. Very interesting.

gravatar
Sergi 2011-06-15 18:53

@Eduardo,

You're most welcome :)

gravatar
FT 2012-01-12 23:16

Very nice solution, Sergei! thanks for Sharing, i was about to put in an ugly hack when i ran across your post! You rock.

gravatar
Siggi 2012-01-24 11:27

Thanks a lot for this post! In my case I was passing dynamic objects FROM the MyProject.Test to the MyProject cause a class in MyProject was working with dynamic objects which I was mocking with Moq, so I had to put InternalsVisibleTo("MyProject.Test") into the MyProject AssemblyInfo.cs (so I could test internals of MyProject in MyProject.Test) AND ALSO put InternalsVisibleTo("MyProject") in MyProject.Test AssemblyInfo.cs so the code in MyProject could find some properties on the dynamic anonymous object :)

this post got me on the right when I realized that anonymous objects are internal, thanks again!!

gravatar
Sergi 2012-01-24 11:51

@FT, @Siggi,

You're welcome; I'm glad I could help.

And thank you for your comments! ;)

Leave a comment