Using C# anonymous types to assert complex results in your unit tests

Does this sounds familiar to you:

You come to work early(ish) in the morning – ready to apply a new technology you’ve just learnt about – just to find out that you can’t. Your frustration grows as you understand that in order to use this “new and shiny” some adjustments must be made and what seemed so easy in last week training/conference/blog is in fact more complex in your project…

In my experience one of the reason developers fail unit testing is the gap that exist between learning the basics of unit testing and TDD and applying it to your project – you have existing code to deal with, problematic dependencies, and on top of that most of the time you deal with complex data which you need to create and/or check at the end of your test.

Consider a “simple” customer class:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string EmailAddress { get; set; }
    public string TelephoneNumber { get; set; }
    public DateTime Birthday { get; set; }
    public string Occupation { get; set; }
    public string Domain { get; set; }
    public Address Address { get; set; }
}

That customer has many fields some of which I don’t care about in all of my tests and on top of that it has nested classes – in this example an Address.

If you try to use Assert.AreEqual in your unit testing of choice it would probably fail. This is caused by the fact that most unit testing frameworks would use the default equality functionality would only test if both instances have the same reference. On top of that you would probably get a cryptic assertion failure:

TestProject1.RunMfgSiteSeqTest.SimpleEqual

  Expected: <TestProject1.TestClasses.Customer>
  But was:  <TestProject1.TestClasses.Customer>

Obviously there are solutions for this specific issue – you can override the equality operator (depending of your language of choice) or use a 3rd party assertion library such as FluentAssertions/Shouldly (C#) or Hamcrest (Java) or use the multiple assertions capability that was recently added to both NUnit and Java (or use mine) but all of those are tedious, and not always possible depending on your project. The issue becomes unbearable when you need to assert a collection of such objects with expected results.

I’ve wanted to find an easy way to check a list of objects – of any kind with a list of expected objects, only comparing some of the properties and get a good error message when assertion fails. The project was using C# (.NET 4.6) and so I’ve looked for a supported language construct that would auto-magically implement comparison and to string for me, after a bit of thinking I’ve decided to try using anonymous types.

Anonymous types were introduced to help create ad-hoc instances usually during Linq queries essentially by writing:

new { Id = 1234, Name = "Dror Helper" }

I would create a new type in my code that has two properties “Id” and “Name” and the cool thing is that I get Equals, HashCode and ToString automatically implemented based on those properties. And so I was able to write the following test:

[Test]
public void GetAllCustomers_CustomersNameAndCountryAsExpectedSimple()
{
    var result = GetAllCustomers();
    var resultAsAnonymous = result.Select(customer =>
        new {
            customer.Name, 
            Address = new
            {
                customer.Address.Country
            }
        }); 
    
    var expected = new[]
    {
        new {Name = "Nathan Salazar", Address = new {Country = "US"}},
        new {Name = "Lani Decker", Address = new {Country = "UK"}},
        new {Name = "Wade wrong", Address = new {Country = "France"}},
        new {Name = "Sonia Page", Address = new {Country = "US"}},
    };

    CollectionAssert.AreEqual(expected, resultAsAnonymous);
}

I order to compare the result to my expected I needed to put the data I’ve wanted to compare into another anonymous class and then use CollectionAssert.AreEqual to compare the result to my expected data. And presto, I could compare my complex object to another object, and if (read: when) my test failed I could immediately understand why:

AssertionUsingAnonymousTypes.AssertEqualsTest.GetAllCustomers_CustomersNameAndCountryAsExpectedSimple

  Expected is <<>f__AnonymousType0`2[System.String,<>f__AnonymousType1`1[System.String]][4]>, actual is <System.Linq.Enumerable+WhereSelectListIterator`2[AssertionUsingAnonymousTypes.TestClasses.Customer,<>f__AnonymousType0`2[System.String,<>f__AnonymousType1`1[System.String]]]>
  Values differ at index [2]
  Expected: <{ Name = Wade wrong, Address = { Country = France } }>
  But was:  <{ Name = Wade Guerr, Address = { Country = France } }>

But using this method means that for each result I would need to create a “transformed” (resultAsAnonymous) class with my data and make sure I compare the correct properties by name – which seemed error prone and quite frankly – too much work.

What I needed is to create a method that would handle the transformation:

  • Receive two parameters: an enumerable of “real” classes and an enumerable of expected anonymous classes
  • Transform each item in the result to an anonymous type based on the expected type
  • Compare/assert

At first it seemed impossible – how can I pass an anonymous type as a parameter, this type will be created by the compiler. and it looks something like this:

[CompilerGenerated]
[DebuggerDisplay("\\{ Name = {Name}, Address = {Address} }", Type = "<Anonymous Type&gt;")]
internal sealed class \u003C\u003Ef__AnonymousType0<\u003CName\u003Ej__TPar, \u003CAddress\u003Ej__TPar&gt;
{
  [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  private readonly \u003CName\u003Ej__TPar \u003CName\u003Ei__Field;
  [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  private readonly \u003CAddress\u003Ej__TPar \u003CAddress\u003Ei__Field;

  public \u003CName\u003Ej__TPar Name
  {
    get
    {
      return this.\u003CName\u003Ei__Field;
    }
  }

  public \u003CAddress\u003Ej__TPar Address
  {
    get
    {
      return this.\u003CAddress\u003Ei__Field;
    }
  }

But then it hit me – I don’t really need to know the type name as long as .NET does – which I can do using Generics:

public static void AssertEqualTo<TActual, TExpected&gt;(this IEnumerable<TActual&gt; actual, IEnumerable<TExpected&gt; expectedAnonymous)
{
    var expectedType = expectedAnonymous.First().GetType();
    var transformedResult = new List<TExpected&gt;();
    foreach (var item in actual)
    {
        var actualType = item.GetType();
        var result = (TExpected)TransformRealToAnonymous(item, actualType, expectedType);
        transformedResult.Add(result);
    }

    CollectionAssert.AreEqual(expectedAnonymous, transformedResult);
}

I’ve wrote an extension method that excepts two parameters using the magic of generics and then convert each item from TActual to TExpected, I am assuming that both have properties with similar names and finally I use built in assertion to check the values.

The real magic happens in the TransformRealToAnontmous method:

private static object TransformRealToAnonymous(object instance, Type realType, Type anonymousType)
{
    var ctor = anonymousType.GetConstructors().Single();
    var constructorParameters = ctor.GetParameters()
        .Select(parameter => GetValue(instance, realType, parameter))
        .ToArray();
    return ctor.Invoke(constructorParameters);
}

private static object GetValue(object instance, Type instanceType, ParameterInfo parameterInfo)
{
    var actualProperty = instanceType.GetProperty(parameterInfo.Name);
    var actualPropertyValue = actualProperty.GetValue(instance);
    if (!parameterInfo.ParameterType.IsAnonymousType())
    {
        return actualPropertyValue;
    }
    return TransformRealToAnonymous(actualPropertyValue, actualProperty.PropertyType, parameterInfo.ParameterType);
}

Basically I’m counting on another aspect of C# anonymous types – they have a constructor that accepts a list of parameters ordered and named according to the property names:

[DebuggerHidden]
  public \u003C\u003Ef__AnonymousType0(
    \u003CName\u003Ej__TPar Name,
    \u003CAddress\u003Ej__TPar Address)
  {
    // ISSUE: reference to a compiler-generated field
    this.\u003CName\u003Ei__Field = Name;
    // ISSUE: reference to a compiler-generated field
    this.\u003CAddress\u003Ei__Field = Address;
  }

And so I was able to “grab” the value from the real class and pass it to the constructor of the new anonymous class. While the code is confusing and might require more than one reading to understand – the end result is simple to use:

[Test]
public void GetAllCustomers_CustomersNameAndCountryAsExpected()
{
    var result = GetAllCustomers();
    
    var expected = new[]
    {
        new {Name = "Nathan Salazar", Address = new {Country = "US"}},
        new {Name = "Lani Decker", Address = new {Country = "UK"}},
        new {Name = "Wade wrong", Address = new {Country = "France"}},
        new {Name = "Sonia Page", Address = new {Country = "US"}},
    };

    result.AssertEqualTo(expected);
}

Create an anonymous type with the same property names of the real class you want to compare and that’s it.

If you want to try it out the code is available on GitHub for your debugging pleasure.

I hope this trick would be useful to you as it was to me. Try it in your next unit testing project and hopefully it will remove one of the hurdles each developer attempting unit testing must face.

And until then – happy coding…

4 thoughts on “Using C# anonymous types to assert complex results in your unit tests

  1. I feel like the expected end of the comparison loses the compile-time type safety which is obviously a strength of C#.

    Obviously at run-time, tests will take this into account, but it could add an extra step when doing things like renaming properties

    1. You’re right, Like all things software related it’s a trade-off, Once you start comparing two different types one can change without the other, I found other solutions such as implementing equals, external compaeres and using Assert.Multiple to suffer from similar issues so far this is the best solution for a specific kind of test I’ve found but it does not mean I’ll stop looking for better ways

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.