Testing instances of anonymous types using the ‘dynamic’ keyword

Recently I’ve been writing a lot of tests that exercise ASP.NET MVC controllers. Sometimes those controllers return JSON data, and the natural way to express that is with anonymous types – the syntax and structure match JSON very well. However, if I suddenly wish to assert on those objects, things get a bit tricky: there’s no statically typed way to access the properties.

JsonResult has a property called Data which is typed as an Object. I figured if I’d cast that as a dynamic and then use runtime binding, I’d be set. So I wrote a bit of test code:

public void Returns_error_when_list_is_not_found() {
    var controller = new HomeController();
    var result = (JsonResult) controller.AddItemToList(“item”);
    dynamic resultData = result.Data;
    Assert.AreEqual(“Error”, resultData.Status);

and follow up with a bit of implementation code:

public ActionResult AddItemToList(string item) {
 new JsonResult {Data = new {Status = “Fail”}};

(Note: the value of Status in the implementation code is intentionally different from the one I’m asserting against in the test – we want a red light first!)

Seems simple enough, right? So I hit “Run test” and was rather baffled: instead of seeing an assertion error I saw this:

Test result showing unexpected exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : 'object' does not contain a definition for 'Status'

OK, I thought, maybe I’m just looking at the wrong thing. I fired up the same test in the debugger and checked the contents of resultData. It looked like this:

Debugger clearly shows that the instance has a property called Status

So to be sure, the object actually was an instance of my anonymous type. So what’s up with the exception?

It turns out that anonymous types are always internal. Which makes sense, because there’s no sane way to represent the type at assembly or even method boundaries. However, since dynamic came along, there is a straightforward way to manipulate the objects beyond those boundaries if you just ship them across as plain Objects.

There are, of course, a couple of obvious solutions: one is to make the bits I want to manipulate statically typed. One is to futz around with reflection, but I try to keep that to a minimum. The one I chose for now, is to mark the assembly under test with

[assembly: InternalsVisibleTo(“TestProject”)]

… which does away with the problem, and now we get the expected error:

Test result showing the expected error

Another battle won, another C# compiler factoid learned.

Tests reported twice when using XUnit with TeamCity

Here’s a quickie for all you people running XUnit tests in a TeamCity build. TeamCity doesn’t directly support XUnit, so it takes a wee bit of effort to get things going. The way I decided to tackle the issue was to add the following bits to our test project build file:

<Target Name=CustomBuild DependsOnTargets=Build>
  <CallTarget Targets=Test Condition=$(TEAMCITY_PROJECT_NAME) != ” />
UsingTask AssemblyFile=..\packages\xunit.1.6.1\Tools\xunit.runner.msbuild.dll  TaskName=Xunit.Runner.MSBuild.xunit
<Target Name=Test>
  <xunit Assembly=$(TargetPath) NUnitXml=..\..\TestReport.xml />

The CustomBuild target depends on Build, so Build gets run in any case. This ensures that we always run the latest set of tests.

Then, if the build script detects TeamCity (by presence of the TEAMCITY_PROJECT_NAME variable), it runs the Test target, which outputs its results to TestReport.xml.

Having got this far, I added TestReport.xml to our TeamCity configuration, and things seemed to work nicely. Except that our tests got reported twice.

It took me a while to finally clue in to what was happening: TeamCity was already parsing the output of the XUnit test task, and having a separate test report was what caused the duplicates. This wasn’t immediately obvious to me, until we built a separate performance test bench and used console output to communicate its results to TeamCity (more on that in a future installment).

Long story short: TeamCity can already understand XUnit tests, it just doesn’t provide a way to run them.