internal static void AssertPublicPropertiesEqual(object expected, object actual, string objectDescription, string message, IgnoreProperties ignoreProperties, Stack <object> visitedObjects) { if (string.IsNullOrEmpty(objectDescription)) { objectDescription = "<object>"; } string assertMsg = string.IsNullOrEmpty(objectDescription) ? string.Empty : "(Property: " + objectDescription + ")"; if (!string.IsNullOrEmpty(message)) { assertMsg += (assertMsg.Length > 0 ? " " : string.Empty) + message; } if ((expected == null) || (actual == null)) { // Either expected or actual is null, so assert that both are. Assert.AreEqual(expected, actual, assertMsg); } else { // Neither expected nor actual is null. bool haveAddedToVisitedObjects; if (StartVisit(expected, visitedObjects, out haveAddedToVisitedObjects)) { // Looks like the caller's original type contains a circular reference - we have already seen [expected]. return; } // Assert that expected and actual are of the same type. Assert.AreSame(expected.GetType(), actual.GetType(), string.Format("Objects are not of the same type. {0}", assertMsg)); Type objectType = expected.GetType(); bool checkObjectPublicProperties = !(objectType.IsPrimitive || objectType.IsEnum || (expected is string) || expected is DateTime); // For these types there is no need to check the public properties. bool isValueTypeObjectWithoutRefProperties = objectType.IsValueType; // While checking public properties, we will keep track of whether the object is a value type that contains only value type properties (in which case we will check bitwise equality). if (checkObjectPublicProperties) { // See if the caller has supplied an ignore list. bool hasIgnoreList = (ignoreProperties != null) && (ignoreProperties.Count > 0); foreach (PropertyInfo currProperty in objectType.GetProperties()) { isValueTypeObjectWithoutRefProperties &= currProperty.PropertyType.IsValueType; 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))) { // This is not a static property, not an indexed property and it is not on the ignore list so check it. object expectedPropValue = currProperty.GetValue(expected, null); object actualPropValue = currProperty.GetValue(actual, null); string propertyDescription = (objectDescription == null) ? currProperty.Name : string.Format("{0}.{1}", objectDescription, currProperty.Name); AssertPublicPropertiesEqual(expectedPropValue, actualPropValue, propertyDescription, message, ignoreProperties, visitedObjects); } // is non-indexed and not on ignore list } // has a Get method } // foreach property in object if (typeof(IEnumerable).IsAssignableFrom(objectType)) { // Object is some kind of collection. Enumerate through all the objects it contains. IEnumerator it_e = ((IEnumerable)expected).GetEnumerator(); IEnumerator it_a = ((IEnumerable)actual).GetEnumerator(); int count = 0; bool moreItemsInExpected = true; while (moreItemsInExpected) { if (moreItemsInExpected = it_e.MoveNext()) { Assert.IsTrue(it_a.MoveNext(), string.Format("Expected more items in collection; actual has only {0} item(s). {1}", count, assertMsg)); count++; AssertPublicPropertiesEqual(it_e.Current, it_a.Current, string.Format("{0}[{1}]", objectDescription, count - 1), message, ignoreProperties, visitedObjects); } else { Assert.IsFalse(it_a.MoveNext(), string.Format("Expected {0} items in collection, but actual has more than that. {1}", count, assertMsg)); } } // while there are more items in the enumeration } // object is enumerable } // check object's public properties bool checkObjectValueEquality = isValueTypeObjectWithoutRefProperties || (expected is string); if (checkObjectValueEquality) { Assert.AreEqual(expected, actual, assertMsg); } if (haveAddedToVisitedObjects) { EndVisit(visitedObjects); } } // object is not null }
/// <summary> /// Asserts that the public properties of two objects are equal (a deep comparison). /// </summary> /// <param name="expected">The first object to compare. This is the object the unit test expects.</param> /// <param name="actual">The second object to compare. This is the object the unit test produced.</param> /// <param name="ignoreProperties">An <see cref="IgnoreProperties"/> object that specifies the properties that are to be ignored during the comparison.</param> /// <param name="message">A message to display if the assertion fails. This message can be seen in the unit test results.</param> public static void AssertPublicPropertiesEqual(object expected, object actual, IgnoreProperties ignoreProperties, string message) { AssertPublicPropertiesEqual(expected, actual, null, message, null, new Stack <object>()); }