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

  1. Malathi
    Posted March 18, 2011 at 10:36 am | Permalink

    Hi,

    this is a very nice code but can u let me know how to compare the values between the two enumerable showed above. can u give me ur email ID

  2. Posted March 18, 2011 at 11:39 am | Permalink

    If you are asking how to compare two properties that are enumerable, the method should already do that. To prove this, I wrote the following test cases:

    public enum TestValues
    {
        TestValue1,
        TestValue2
    }
     
     
    public class TestClass3
    {
        public TestValues Value1 { getset; }
    }
     
     
    [ Test ]
    public void comparing_two_instances_with_the_same_enum_value_should_pass()
    {
        var obj1 = new TestClass3 {Value1 = TestValues.TestValue1};
        var obj2 = new TestClass3 {Value1 = TestValues.TestValue1};
     
        obj1.should_be_equal_toobj2 );
    }
     
     
    [ Test ]
    public void comparing_two_instances_with_different_enum_values_should_fail()
    {
        var obj1 = new TestClass3 {Value1 = TestValues.TestValue1};
        var obj2 = new TestClass3 {Value1 = TestValues.TestValue2};
        Assert.Throws< AssertionException >( () => obj1.should_be_equal_toobj2 ) );
    }

    Hopefully that answers your question.

    I will email you with my contact information as you requested.

    -Chris

  3. John
    Posted October 4, 2011 at 12:51 pm | Permalink

    Hi,

    Given the class:

    public class Person : IPerson, IId
    {
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public int Age { get; set; }

    public int Id { get; set; }
    }

    It appears that the string properties are skipped over on comparison. Any idea why?

  4. John
    Posted October 4, 2011 at 1:30 pm | Permalink

    Apologies, I just saw the line in GetComparableProperties – !property.PropertyType.IsClass

Post a Comment

Your email is never published nor shared. Required fields are marked *

You may use these HTML tags and attributes <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*
*