A Dynamic Equality Assertion Method in C#

Ever wanted to assert two objects were equal without having to manually compare their public properties one at a time? Now you can. This method asserts the equality of any two objects by comparing the values of all public, readable properties that have matching names and types. It will compare them even if the objects are different types. This may very well be the last object assertion method you will ever need!

Usage

First let’s look at how we would use the method by looking at the tests. The method is called should_be_equal_to().

public class TestClass1
{
	public int Value1 { get; set; }
	internal int Value2 { get; set; }
}

public class TestClass2
{
	public int Value1 { get; set; }
}

[ TestFixture ]
public class GlobalEqualityAssertionTests
{
	[ Test ]
	public void comparing_two_identical_instances_of_the_same_class_should_pass()
	{
		var obj1 = new TestClass1 {Value1 = 1};
		var obj2 = new TestClass1 {Value1 = 1};
		obj1.should_be_equal_to( obj2 );
	}

	[ Test ]
	public void comparing_two_instances_of_different_classes_that_have_the_same_public_property_where_both_instances_have_the_same_value_for_that_property()
	{
		var obj1 = new TestClass1 {Value1 = 1};
		var obj2 = new TestClass2 {Value1 = 1};
		obj1.should_be_equal_to( obj2 );
	}

	[ Test ]
	public void comparing_two_instances_of_different_classes_that_have_the_same_public_property_where_the_values_for_that_property_differ()
	{
		var obj1 = new TestClass1 {Value1 = 1};
		var obj2 = new TestClass2 {Value1 = 2};
		Assert.Throws< AssertionException >( () => obj1.should_be_equal_to( obj2 ) );
	}

	[ Test ]
	public void comparing_two_instances_of_the_same_class_where_an_internal_property_differs_should_pass()
	{
		var obj1 = new TestClass1 {Value1 = 1, Value2 = 1};
		var obj2 = new TestClass1 {Value1 = 1, Value2 = 3};
		obj1.should_be_equal_to( obj2 );
	}

	[ Test ]
	public void comparing_two_instances_of_the_same_class_with_different_values_should_fail()
	{
		var obj1 = new TestClass1 {Value1 = 1};
		var obj2 = new TestClass1 {Value1 = 2};
		Assert.Throws< AssertionException >( () => obj1.should_be_equal_to( obj2 ) );
	}
}

The Code

Here is the main part of the method…

public static void should_be_equal_to< TActual, TExpected >( this TActual actual, TExpected expected )
{
	foreach ( PropertyPair propertyPair in GetPropertiesToCompare( actual, expected ) )
	{
		object actualValue = propertyPair.Actual.GetValue( actual, null );
		object expectedValue = propertyPair.Expected.GetValue( expected, null );

		Assert.That( actualValue, Is.EqualTo( expectedValue ),
			string.Format( "Expected {0} to match, but did not.", propertyPair.Expected.Name ) );
	}
}

This code loops over all the PropertyPairs we want to compare and compares them. If any value does not match it will fail the assertion with a meaningful error message.

The PropertyPair class is implemented as a private inner class within the assertion class.

private class PropertyPair
{
	public PropertyInfo Expected { get; set; }
	public PropertyInfo Actual { get; set; }
}

Lets look at the code that creates these pairs.

private static IEnumerable< PropertyPair > GetPropertiesToCompare< TActual, TExpected >( TActual actual, TExpected expected )
{
	return
		from exp in GetComparableProperties( expected )
		join act in GetComparableProperties( actual )
				on new {exp.Name, exp.PropertyType}
				equals new {act.Name, act.PropertyType}
		select new PropertyPair {Actual = act, Expected = exp};
}

private static IEnumerable< PropertyInfo > GetComparableProperties< T >( T actual )
{
	return GetPublicGetProperties( actual )
					.Where( property =>
							!property.PropertyType.IsClass &&
							!property.PropertyType.IsInterface
					);
}

private static IEnumerable< PropertyInfo > GetPublicGetProperties< T >( T obj )
{
	Type type = obj.GetType();

	return type.FindMembers( MemberTypes.Property,
					 BindingFlags.Public | BindingFlags.Instance,
					 ( m, f ) => ( (PropertyInfo)m ).CanRead,
					 null )
					.Cast< PropertyInfo >();
}

GetPropertiesToCompare() simply gets the comparable properties for each object type and joins them on name and type. Any properties that don’t match on name and type are ignored. Those that do match are put into a PropertyPair object to be returned.

GetComparableProperties() calls GetPublicGetProperties() to get a list of public readable properties. It then throws out any reference type properties so that only value properties are left. These are the properties that get compared.

Caveats

Note that reference properties (properties that refer to other objects) are not compared, so even if they differ, the objects will show as equal. I have deferred this task since I still need consider how to handle references we don’t want to compare (and there are many).

Where Can I Get It?

This code can be found on my Shiloh.Testing github repository. The link goes to the v0.1 tag, which only has this class and its supporting classes and tests. It includes several other features that I stripped out to make this blog post simpler, like the ability to clean up values before comparing them, and an attribute you can place on properties you want to ignore when asserting equality.

I hope this simplifies your testing…it has definitely simplified mine.

-Chris

This entry was posted in .NET, C#, Testing and tagged , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

4 Comments