public static void ValidateStringKeyAndValue( string name, IDictionary <string, object> dictionary, string key, object value, Action <string, string, string> failedValidationAction) { var entryExists = dictionary.ContainsKey(key); var actualValue = entryExists ? dictionary[key] : null; DeepEqualityResult result = null; if (!entryExists || Reflection.AreNotDeeplyEqual(value, actualValue, out result)) { failedValidationAction( name, $"to have entry with '{key}' key and the provided value", $"{(entryExists ? $"the value was different. {result}" : "such was not found")}"); } }
/// <summary> /// Test if the all values of objects properties are equals recursively /// </summary> /// <param name="expected">The expected object</param> /// <param name="actual">Actual object</param> /// <param name="processedElements"> /// Table that binds a managed object, which is represented by a key, to its attached property, which is represented by a value. /// Automatically removes the key/value entry as soon as no other references to a key exist outside the table. /// </param> /// <param name="result">For which property equality was broken</param> /// <returns></returns> private static bool AreDeeplyEqual( object expected, object actual, ConditionalWeakTable <object, object> processedElements, DeepEqualityResult result) { result.ApplyValues(expected, actual); if (expected == null && actual == null) { return(result.Success); } if (expected == null || actual == null) { return(result.Failure); } var expectedType = expected.GetType(); if (expectedType != typeof(string) && !expectedType.GetTypeInfo().IsValueType) { if (processedElements.TryGetValue(expected, out _)) { return(result.Success); } processedElements.Add(expected, expected); } var actualType = actual.GetType(); var objectType = typeof(object); if ((expectedType == objectType && actualType != objectType) || (actualType == objectType && expectedType != objectType)) { return(result.Failure); } var stringType = typeof(string); if (expected is IEnumerable && expectedType != stringType) { return(CollectionsAreDeeplyEqual(expected, actual, processedElements, result)); } var expectedTypeIsAnonymous = IsAnonymousType(expectedType); if (expectedTypeIsAnonymous) { var actualIsAnonymous = IsAnonymousType(actualType); if (!actualIsAnonymous) { return(result.Failure); } } if (!expectedTypeIsAnonymous && expectedType != actualType && !expectedType.IsAssignableFrom(actualType) && !actualType.IsAssignableFrom(expectedType)) { return(result.Failure); } if (expectedType.GetTypeInfo().IsPrimitive || expectedType.GetTypeInfo().IsEnum) { return(expected.ToString() == actual.ToString() ? result.Success : result.Failure); } var equalsOperator = expectedType.GetMethods().FirstOrDefault(m => m.Name == "op_Equality"); if (equalsOperator != null) { var equalsOperatorResult = (bool)equalsOperator.Invoke(null, new[] { expected, actual }); if (!equalsOperatorResult && expectedType != stringType) { result.PushPath("== (Equality Operator)"); if (!expectedType.IsDateTimeRelated()) { result.ClearValues(); } } return(equalsOperatorResult ? result.Success : result.Failure); } if (expectedType != objectType && !expectedTypeIsAnonymous) { var equalsMethod = expectedType.GetMethods() .FirstOrDefault(m => m.Name == "Equals" && m.DeclaringType == expectedType); if (equalsMethod != null) { var equalsMethodResult = (bool)equalsMethod.Invoke(expected, new[] { actual }); if (!equalsMethodResult) { result .PushPath("Equals()") .ClearValues(); } return(equalsMethodResult ? result.Success : result.Failure); } } if (ComparablesAreDeeplyEqual(expected, actual, result)) { return(result.Success); } if (!ObjectPropertiesAreDeeplyEqual(expected, actual, processedElements, result)) { return(false); } return(true); }
/// <summary> /// Checks whether two objects are deeply equal by reflecting all their public properties recursively. Resolves successfully value and reference types, overridden Equals method, custom == operator, IComparable, nested objects and collection properties. /// </summary> /// <param name="expected">Expected object.</param> /// <param name="actual">Actual object.</param> /// <param name="result">Result object containing differences between the two objects.</param> /// <returns>True or false.</returns> public static bool AreDeeplyEqual(object expected, object actual, out DeepEqualityResult result) { result = new DeepEqualityResult(expected?.GetType(), actual?.GetType()); return(AreDeeplyEqual(expected, actual, new ConditionalWeakTable <object, object>(), result)); }
private static bool ComparablesAreDeeplyEqual(object expected, object actual, DeepEqualityResult result) { if (expected is IComparable expectedAsComparable) { if (expectedAsComparable.CompareTo(actual) == 0) { return(result.Success); } } if (ObjectImplementsIComparable(expected) && ObjectImplementsIComparable(actual)) { var methodName = "CompareTo"; var method = expected.GetType().GetMethod(methodName); if (method != null) { var compareToResult = (int)method.Invoke(expected, new[] { actual }) == 0; if (!compareToResult) { result.PushPath($"{methodName}()"); } return(compareToResult); } } return(result.Failure); }
private static bool AreNotDeeplyEqual( object expected, object actual, ConditionalWeakTable <object, object> processedElements, DeepEqualityResult result) => !AreDeeplyEqual(expected, actual, processedElements, result);
/// <summary> /// Checks whether two objects are not deeply equal by reflecting all their public properties recursively. Resolves successfully value and reference types, overridden Equals method, custom == operator, IComparable, nested objects and collection properties. /// </summary> /// <param name="expected">Expected object.</param> /// <param name="actual">Actual object.</param> /// <param name="result">Result object containing differences between the two objects.</param> /// <returns>True or false.</returns> /// <remarks>This method is used for the route testing. Since the ASP.NET Core MVC model binder creates new instances, circular references are not checked.</remarks> public static bool AreNotDeeplyEqual(object expected, object actual, out DeepEqualityResult result) => !AreDeeplyEqual(expected, actual, out result);
public static ResponseModelAssertionException From(string messagePrefix, DeepEqualityResult result) => new ResponseModelAssertionException($"{messagePrefix} the response model to be the given model, but in fact it was a different one. {result}.");