Thursday, July 20, 2017

Unit Testing Web API when the Result is an Anonymous Object

Sometimes it seems like each day opens my eyes to some new challenge or trick.  If only I could find the time to write about them all.  Today I discovered you can return anonymous objects from a WebAPI controller.  Returning the data was actually the easy part, but being able to unit test it turned out to be a little bit tricky.

I found this answer on Stack Overflow, which pointed to this blog post by Patrick Desjardins that ultimately helped me figure out what needed to be done, but I wanted to document the actual steps I took to make this all work.

The scenario was that we had a custom object that had a bunch of properties on it.  Our UI needed all of that information plus one more field, but we weren't able to just add another property to our object because reasons (that part isn't important).  We also didn't want to create a brand new object to contain our original object plus this other field.  The obvious solution was to return an anonymous type from our controller.

Let's say our object is book and it has a couple of properties on it.  It looks like this:

   1:  public class Book
   2:  {
   3:      public string Author { get; set; }
   4:          
   5:      public string ISBN { get; set; }
   6:  }

When we return our object to the UI we also need to include a StoreId.  What we want to return will look like this:
{
  StoreId: 12345,
  Book: {
    Author: "Herman Melville"
        
    ISBN: "978-1853260087"
  }
}

What we ended up with was a controller method that looks like this:
   1:  [HttpGet]
   2:  public async Task<IHttpActionResult> GetBook()
   3:  {
   4:      var book = new Book
   5:      {
   6:          Author = "Herman Melville",
   7:          ISBN = "978-1853260087"
   8:      };
   9:   
  10:      var result = new {StoreId = 12345, Book = book};
  11:      return Ok(result);
  12:  }

In order to test this we have to do two things.  For starters let's say our Web API project is called Bookstore.Api and our unit tests are in a separate project called Bookstore.Api.UnitTests.  We need to modify the AssemblyInfo.cs file in Bookstore.Api to make internal types available to Bookstore.Api.UnitTests because the anonymous object we're returning from the GetBook method becomes an internal type when it's created.  To do this, just open AssemblyInfo.cs in Bookstore.Api and add this line (where it gets added doesn't seem to matter, but the name absolutely does matter):
[assembly: InternalsVisibleTo("Bookstore.Api.UnitTests")]

Important Note: In .Net Core 2.0 (at least) you have to create the AssemblyInfo.cs file manually as it is no longer generated automatically when you create the file. I just expanded the Properties folder, right-clicked > Add > Class > AssemblyInfo.cs and added the above attribute as the only thing in the file. I did have to include the using statement for System.Runtime.CompilerServices as well.

Finally we can write our test to check the return value of the StoreId in our unit test:
   1:  [TestMethod]
   2:  public async Task GetBookShouldReturnStoreIdWithBook()
   3:  {
   4:      dynamic result = await bookController.GetBook();
   5:   
   6:      Assert.AreEqual(12345, result.Content.StoreId);
   7:  }

It's really important to use dynamic as the return type (as much as I hate doing that) because it's the only way the compiler won't make angry faces at you.  I hope this helps someone (or future me).