Click here to Skip to main content
Email Password   helpLost your password?

Introduction

A while ago, I was looking for a way to compare two objects as part of a unit test. I didn't want Assert.AreSame() because I didn't just want to see if they were the same object, and I didn't want Assert.AreEqual() because I didn't want to have to override Equals(). Ideally I wanted a general utility that would compare any two objects and say whether they were equal. I decided to define “equal” as “having the same value for all public properties”. I wanted a deep comparison, so if a public property is another object then all of its public properties should also be checked.

My search led me to the AssertGraphPropertiesEqual function written by Keith Brown. This was just the kind of thing I had in mind, but in using it I made a number of changes that I think made it generally more useful. I would like to share my version with you. These are the main changes in my version:

  1. I named it AssertPublicPropertiesEqual, but feel free to change that name if you don't like it.
  2. It is more consistent in its handling of an object that is passed as a parameter to the method and an object that is a property of that parameter. Making this change solved a number of problems that I was having with the original.
  3. It attempts to detect and short-circuit circular references.
  4. It allows you to supply an ‘ignore list’ of properties. For example, you might consider two List<string> objects that contain the same strings to be equal even though their Capacity property values are different.
  5. It ignores indexed properties (such as String.Chars). Normally these will be covered by the check of some other non-indexed property.
  6. The original contains this test: pi.PropertyType.IsAssignableFrom(typeof(IEnumerable)). That seemed to be the wrong way around, so I changed it to typeof(IEnumerable).IsAssignableFrom(objectType).

Using the Code

The method is intended to be used in unit testing, so it uses standard Asserts to perform the comparisons. I have tried it with both Visual Studio Team System (VSTS) and NUnit. Just include the appropriate using statement for the library that you use.

A typical call would be something like this:

MyClass expectedObject = the expected result of the test;
MyClass actualObject = MethodBeingTestedThatReturnsAMyClassObject(); 
UnitTestingHelper.AssertPublicPropertiesEqual(expectedObject, actualObject);

As I mentioned above, you can specify properties to be ignored during the comparison. Here's how to ignore the Capacity property of List<string> objects:

MyClass expectedObject = the expected result of the test;
MyClass actualObject = MethodBeingTestedThatReturnsAMyClassObject();

IgnoreProperties ignoreProps = new IgnoreProperties();
ignoreProps.Add(new PropertyComparisonExclusion(typeof(List<string>), "Capacity"));

UnitTestingHelper.AssertPublicPropertiesEqual
    (expectedObject, actualObject, ignoreProps);

When defining PropertyComparionExclusions, you can use the typeAction property to specify whether to ignore the property only on the specified type, or on the type and all its derived types. The default is PropertyComparisonExclusionTypeAction.MatchExactType, meaning that the property is ignored only on the specified type.

Conclusion

I have two questions:

  1. Is this useful for you?
  2. Have I got it right?

I've thrown various objects at it, but the possible inputs are literally infinite so please let me know if you find a problem with it.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
Question[My vote of 1] Didnt work for me - Stack overflow error.
MS_Jocker
17:37 11 Mar '09  
check it with a Form type having two in member in it as
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

public string s = "";
public int i = 10;
}

Form1 f1 = new Form1()
Form1 f2 = new Form1()

Stack overflow error occurred.
AnswerRe: [My vote of 1] Didnt work for me - Stack overflow error.
MS_Jocker
17:52 11 Mar '09  
and also for a simple class it didnt work for me, i just used your file in my solution, modifying the namespace required for unit test framework of VS2008. but still it didnt work , dont know what the problem.

please test for a class.

class temp
{
int a ;
string s;
public temp(int k,string t)
{
a = k;
s = t;
}
}
temp t = new temp(10,"sam");
temp s = new temp(20,"saml");

UnitTestingHelper.AssertPublicPropertiesEqual(t, s);
GeneralRe: [My vote of 1] Didnt work for me - Stack overflow error.
Carl Johansen
1:43 12 Mar '09  
Looking at your simple class first...

AssertPublicPropertiesEqual tests public properties only. temp.a and temp.s are not public and they are not properties! I changed the declaration lines to these:

public int a {get; set;}
public string s { get; set; }

...and then AssertPublicPropertiesEqual(t, s) flagged their inequality.

Carl.
GeneralRe: [My vote of 1] Didnt work for me - Stack overflow error.
MS_Jocker
19:04 12 Mar '09  
Thanks for your reply,It works now.
isnt there any way to compae based on private members also. i know we cannt access the private members but still.
AnswerRe: [My vote of 1] Didnt work for me - Stack overflow error.
Carl Johansen
14:24 23 Mar '09  
Hi,

Sorry for the delay in replying.

Yes, I think it is possible to test private members also. I don't think I want to include this in the downloadable code, but here's the idea:

In the main AssertPublicPropertiesEqual method you will see this loop:

foreach (PropertyInfo currProperty in objectType.GetProperties())
{

}

The idea is to put a copy of this loop directly underneath it and then modify the new copy to work on fields instead of properties. The new foreach looks like this:

foreach (FieldInfo currField in objectType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
{

}

Now there are a few change to make inside the loop copy because we are using FieldInfo instead of PropertyInfo. Here is the full loop code that I tried. I have given it a very quick test and it seems to work but I make no guarantees:

foreach (FieldInfo currField in objectType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
{
isValueTypeObjectWithoutRefProperties &= currField.FieldType.IsValueType;

bool isStaticProperty = currField.IsStatic;

if ((!isStaticProperty) && (!hasIgnoreList || !ignoreProperties.IgnoreProperty(objectType, currField.Name)))
{
//This is not a static property, not an indexed property and it is not on the ignore list so check it.
object expectedPropValue = currField.GetValue(expected);
object actualPropValue = currField.GetValue(actual);
string propertyDescription = (objectDescription == null) ? currField.Name : String.Format("{0}.{1}", objectDescription, currField.Name);

AssertPublicPropertiesEqual(expectedPropValue, actualPropValue, propertyDescription, message, ignoreProperties, visitedObjects);
} // is non-indexed and not on ignore list
} // foreach property in object


HTH,
Carl

AnswerRe: [My vote of 1] Didnt work for me - Stack overflow error.
Carl Johansen
3:34 12 Mar '09  
Good question! The problem is the Color struct, which has a bunch of static properties (eg Color.Transparent) that are also of type Color. This causes an infinite recursion. Having thought about it, I think the best fix is to change my code to ignore static properties. This seems right; if expected and actual are of the same type and that type has a static property then of course its value will be the same for both expected and actual.

I will ask CodeProject to update my download file, but in the meantime you can fix it by replacing this block (starting at line 255)...

bool hasGetMethod = (currProperty.GetGetMethod() != null);
<snip 4 lines>
if ((!isIndexedProperty) && (!hasIgnoreList || !ignoreProperties.IgnoreProperty(objectType, currProperty.Name)))
{ <etc>

...with this:

MethodInfo getterMethod = currProperty.GetGetMethod();
if (getterMethod != null)
{
bool isStaticProperty = getterMethod.IsStatic;
bool isIndexedProperty = (getterMethod.GetParameters().Length > 0);

if ((!isStaticProperty) && (!isIndexedProperty) && (!hasIgnoreList || !ignoreProperties.IgnoreProperty(objectType, currProperty.Name)))
{ <etc>


Now your Form test almost works. There were a couple more idiosyncratic properties of Form that caused problems (due to different auto-generated values in each instance) but I doubt that you care about these so you can ignore them. Thus your call should look like this:

IgnoreProperties ignore = new IgnoreProperties();
ignore.Add(new PropertyComparisonExclusion(typeof(Form1), "AccessibilityObject"));
ignore.Add(new PropertyComparisonExclusion(typeof(Form1), "Handle"));

UnitTestingHelper.AssertPublicPropertiesEqual(form1, form2, ignore);

Also please see my other reply re public properties and be careful with Form1.s and Form1.i.

HTH,
Carl
GeneralJust what I was looking for
DancingJester
5:55 15 Aug '08  
Thanks for this Carl, it's made my unit testing (using VSTS) much easier.
I'll post again should I come across any problems or make any changes.


Last Updated 13 Jan 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010