static void Reject(object expected, object actual, Pair <string> path, ShouldLookLikeOptions options) { var pathString = path.Reverse().Join("/"); if (options.ExceptPaths.Contains(pathString)) { return; } throw new SpecificationException(string.Format("Mismatch at {0}\nExpected : {1}\nActual : {2}", pathString, TestHelperHelpers.Stringify(expected), TestHelperHelpers.Stringify(actual))); }
static void CheckEnumerableStrict(object expected, object actual, ShouldLookLikeOptions options, Pair <string> path, HashSet <object> visitedExpecteds) { var expectedObjects = ToObjectList(expected); var actualObjects = ToObjectList(actual); if (expectedObjects.Count != actualObjects.Count) { Reject(expected, actual, path, options); } var q = expectedObjects.Zip(actualObjects, (e, a) => new { Expected = e, Actual = a }); int c = 0; foreach (var z in q) { Check(z.Expected, z.Actual, options, Bracketize(c).Cons(path), visitedExpecteds); c++; } }
static void Check(object expected, object actual, ShouldLookLikeOptions options, Pair <string> path, HashSet <object> visitedExpecteds) { if (actual == null && expected == null) { return; } if (actual == null || expected == null) { Reject(expected, actual, path, options); } if (visitedExpecteds.Contains(expected)) { return; } visitedExpecteds.Add(expected); var type = actual.GetType(); if (expected.GetType() != type) { Reject(expected, actual, path, options); } if (type.IsValueType || actual is string) { if (!actual.Equals(expected)) { Reject(expected, actual, path, options); } return; } if (actual is System.Collections.IEnumerable) { var allItems = ToObjectList(expected).Concat(ToObjectList(actual)).Where(x => x != null).ToList(); if (!allItems.Any()) { return; } var concreteItemType = allItems.First().GetType(); var enumOption = options.EnumerableOptions.FirstOrDefault(x => x.ItemType.IsAssignableFrom(concreteItemType)) ?? new EnumerableOption(); if (enumOption.Comparison == EnumerableComparison.Strict) { CheckEnumerableStrict(expected, actual, options, path, visitedExpecteds); } else { CheckEnumerableUnordered(expected, actual, options, enumOption, path, visitedExpecteds); } return; } var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy); foreach (var fi in fields) { Check(fi.GetValue(expected), fi.GetValue(actual), options, fi.Name.Cons(path), visitedExpecteds); } var propsWithGetters = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy) .Select(pi => new { Prop = pi, Get = pi.GetGetMethod(false) }).Where(z => z.Get != null); foreach (var z in propsWithGetters) { object propExpected = null; object propActual = null; try { propExpected = z.Get.Invoke(expected, new object[0]); propActual = z.Get.Invoke(actual, new object[0]); } catch (Exception) { Reject(propExpected, propActual, z.Prop.Name.Cons(path), options); return; } Check(propExpected, propActual, options, z.Prop.Name.Cons(path), visitedExpecteds); } }
public static bool LooksLike(this object actual, object expected, ShouldLookLikeOptions options = null) { return(Catch.Exception(() => actual.ShouldLookLike(expected, options)) == null); }
public static void ShouldNotLookLike(this object actual, object expected, ShouldLookLikeOptions options = null) { new Action(() => actual.ShouldLookLike(expected, options)).ShouldThrow <SpecificationException>(); }
/// <summary> /// Asserts that the actual and expected arguments are similar. Similarity is determined by /// recursively comparing the publicly visible fields and properties, if any. /// </summary> public static void ShouldLookLike(this object actual, object expected, ShouldLookLikeOptions options = null) { options = options ?? new ShouldLookLikeOptions(); Check(expected, actual, options, Lists.Create(".").ToPairs(), new HashSet <object>()); }
static void CheckEnumerableUnordered(object expectedEnumerable, object actualEnumerable, ShouldLookLikeOptions options, EnumerableOption enumOption, Pair <string> path, HashSet <object> visitedExpecteds) { var expectedObjects = ToObjectList(expectedEnumerable); var actualObjects = ToObjectList(actualEnumerable); var uncheckedExpected = expectedObjects.ToList(); var uncheckedActual = actualObjects.ToList(); while (true) // would look a lot better if C# had TCO. Then we could do this recursively. { ContinueWhile: if (!uncheckedExpected.Any()) { break; } var expected = uncheckedExpected.First(); if (uncheckedActual.Any()) // we have a chance of matching an actual with the expected and continuing to the next expected { foreach (var actual in uncheckedActual) { bool threw = false; try { var throwAwayHash = new HashSet <object>(); // we need to still guard against cycles, but don't remember anything we saw. Check(expected, actual, options, "?".Cons(path), throwAwayHash); } catch (SpecificationException) { threw = true; } if (!threw) { uncheckedExpected.Remove(expected); uncheckedActual.Remove(actual); goto ContinueWhile; } } } // didn't find a match :( Reject(expected, TestHelperHelpers.MissingValue, Bracketize(expectedObjects.IndexOf(expected)).Cons(path), options); } if (uncheckedActual.Any() && enumOption.Comparison == EnumerableComparison.IgnoreOrder) { var actual = uncheckedActual.First(); Reject(TestHelperHelpers.MissingValue, actual, Bracketize(actualObjects.IndexOf(actual)).Cons(path), options); } }