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.Equal(expected, actual); } 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.Same(expected.GetType(), actual.GetType()); 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.True(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.False(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.Equal(expected, actual); } if (haveAddedToVisitedObjects) { EndVisit(visitedObjects); } } // object is not null }