From f30e0143c945de30ee0485c30932a78c89639991 Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Wed, 26 Sep 2018 20:41:17 +0200 Subject: [PATCH] Comparing an object graph against IEnumerable works now as expected --- .../Collections/CollectionAssertions.cs | 65 ++++++++++++++++++ .../NonGenericCollectionAssertions.cs | 67 ------------------- .../EnumerableEquivalencyValidator.cs | 15 ++++- ...numerableEquivalencyValidatorExtensions.cs | 11 --- .../CollectionEquivalencySpecs.cs | 40 +++++++++++ 5 files changed, 119 insertions(+), 79 deletions(-) diff --git a/Src/FluentAssertions/Collections/CollectionAssertions.cs b/Src/FluentAssertions/Collections/CollectionAssertions.cs index 3f18f89836..a9df23cebd 100644 --- a/Src/FluentAssertions/Collections/CollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/CollectionAssertions.cs @@ -349,6 +349,71 @@ public AndConstraint BeEquivalentTo(params object[] expectations) return new AndConstraint((TAssertions)this); } + /// + /// Asserts that a collection of objects is equivalent to another collection of objects. + /// + /// + /// Objects within the collections are equivalent when both object graphs have equally named properties with the same + /// value, irrespective of the type of those objects. Two properties are also equal if one type can be converted to another + /// and the result is equal. + /// The type of a collection property is ignored as long as the collection implements and all + /// items in the collection are structurally equal. + /// Notice that actual behavior is determined by the global defaults managed by . + /// + public AndConstraint BeEquivalentTo(IEnumerable expectation, string because = "", params object[] becauseArgs) + { + BeEquivalentTo(expectation, config => config, because, becauseArgs); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that a collection of objects is equivalent to another collection of objects. + /// + /// + /// Objects within the collections are equivalent when both object graphs have equally named properties with the same + /// value, irrespective of the type of those objects. Two properties are also equal if one type can be converted to another + /// and the result is equal. + /// The type of a collection property is ignored as long as the collection implements and all + /// items in the collection are structurally equal. + /// Notice that actual behavior is determined by the global defaults managed by . + /// + /// + /// A reference to the configuration object that can be used + /// to influence the way the object graphs are compared. You can also provide an alternative instance of the + /// class. The global defaults are determined by the + /// class. + /// + /// + /// An optional formatted phrase as is supported by explaining why the + /// assertion is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint BeEquivalentTo(IEnumerable expectation, + Func, EquivalencyAssertionOptions> config, string because = "", + params object[] becauseArgs) + { + EquivalencyAssertionOptions options = config(AssertionOptions.CloneDefaults()); + + var context = new EquivalencyValidationContext + { + Subject = Subject, + Expectation = expectation, + RootIsCollection = true, + CompileTimeType = typeof(IEnumerable), + Because = because, + BecauseArgs = becauseArgs, + Tracer = options.TraceWriter + }; + + var equivalencyValidator = new EquivalencyValidator(options); + equivalencyValidator.AssertEquality(context); + + return new AndConstraint((TAssertions)this); + } + /// /// Asserts that a collection of objects is equivalent to another collection of objects. /// diff --git a/Src/FluentAssertions/Collections/NonGenericCollectionAssertions.cs b/Src/FluentAssertions/Collections/NonGenericCollectionAssertions.cs index 89cb6d125d..7f462141d6 100644 --- a/Src/FluentAssertions/Collections/NonGenericCollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/NonGenericCollectionAssertions.cs @@ -301,72 +301,5 @@ private int GetMostLocalCount() return base.NotContain(new[] { unexpected }, because, becauseArgs); } - - /// - /// Asserts that a collection of objects is equivalent to another collection of objects. - /// - /// - /// Objects within the collections are equivalent when both object graphs have equally named properties with the same - /// value, irrespective of the type of those objects. Two properties are also equal if one type can be converted to another - /// and the result is equal. - /// The type of a collection property is ignored as long as the collection implements and all - /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . - /// - /// - /// An optional formatted phrase as is supported by explaining why the - /// assertion is needed. If the phrase does not start with the word because, it is prepended automatically. - /// - /// - /// Zero or more objects to format using the placeholders in . - /// - public void BeEquivalentTo(IEnumerable expectation, string because = "", params object[] becauseArgs) - { - BeEquivalentTo(expectation, config => config, because, becauseArgs); - } - - /// - /// Asserts that a collection of objects is equivalent to another collection of objects. - /// - /// - /// Objects within the collections are equivalent when both object graphs have equally named properties with the same - /// value, irrespective of the type of those objects. Two properties are also equal if one type can be converted to another - /// and the result is equal. - /// The type of a collection property is ignored as long as the collection implements and all - /// items in the collection are structurally equal. - /// - /// - /// A reference to the configuration object that can be used - /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. - /// - /// - /// An optional formatted phrase as is supported by explaining why the - /// assertion is needed. If the phrase does not start with the word because, it is prepended automatically. - /// - /// - /// Zero or more objects to format using the placeholders in . - /// - public void BeEquivalentTo(IEnumerable expectation, - Func, EquivalencyAssertionOptions> config, string because = "", - params object[] becauseArgs) - { - EquivalencyAssertionOptions options = config(AssertionOptions.CloneDefaults()); - - var context = new EquivalencyValidationContext - { - Subject = Subject, - Expectation = expectation, - RootIsCollection = true, - CompileTimeType = typeof(IEnumerable), - Because = because, - BecauseArgs = becauseArgs, - Tracer = options.TraceWriter - }; - - var equivalencyValidator = new EquivalencyValidator(options); - equivalencyValidator.AssertEquality(context); - } } } diff --git a/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidator.cs b/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidator.cs index f8d5b52ce8..b10ca4f5a0 100644 --- a/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidator.cs +++ b/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidator.cs @@ -33,7 +33,7 @@ public EnumerableEquivalencyValidator(IEquivalencyValidator parent, IEquivalency public void Execute(object[] subject, T[] expectation) { - if (AssertIsNotNull(expectation, subject) && EnumerableEquivalencyValidatorExtensions.AssertCollectionsHaveSameCount(subject, expectation)) + if (AssertIsNotNull(expectation, subject) && AssertCollectionsHaveSameCount(subject, expectation)) { if (Recursive) { @@ -59,6 +59,19 @@ private bool AssertIsNotNull(object expectation, object[] subject) .FailWith("Expected {context:subject} to be , but found {0}.", new object[] { subject }); } + private static Continuation AssertCollectionsHaveSameCount(ICollection subject, ICollection expectation) + { + return AssertionScope.Current + .WithExpectation("Expected {context:subject} to be a collection with {0} item(s){reason}", expectation.Count) + .AssertEitherCollectionIsNotEmpty(subject, expectation) + .Then + .AssertCollectionHasEnoughItems(subject, expectation) + .Then + .AssertCollectionHasNotTooManyItems(subject, expectation) + .Then + .ClearExpectation(); + } + private void AssertElementGraphEquivalency(object[] subjects, T[] expectations) { unmatchedSubjectIndexes = new List(subjects.Length); diff --git a/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidatorExtensions.cs b/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidatorExtensions.cs index 55c3384ba9..d1854d9a96 100644 --- a/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidatorExtensions.cs +++ b/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidatorExtensions.cs @@ -6,17 +6,6 @@ namespace FluentAssertions.Equivalency { internal static class EnumerableEquivalencyValidatorExtensions { - public static Continuation AssertCollectionsHaveSameCount(ICollection subject, ICollection expectation) - { - return AssertionScope.Current - .WithExpectation("Expected {context:subject} to be a collection with {0} item(s){reason}", expectation.Count) - .AssertEitherCollectionIsNotEmpty(subject, expectation) - .Then - .AssertCollectionHasEnoughItems(subject, expectation) - .Then - .AssertCollectionHasNotTooManyItems(subject, expectation); - } - public static Continuation AssertEitherCollectionIsNotEmpty(this IAssertionScope scope, ICollection subject, ICollection expectation) { return scope diff --git a/Tests/Shared.Specs/CollectionEquivalencySpecs.cs b/Tests/Shared.Specs/CollectionEquivalencySpecs.cs index 115aed7ad0..438ca41cac 100644 --- a/Tests/Shared.Specs/CollectionEquivalencySpecs.cs +++ b/Tests/Shared.Specs/CollectionEquivalencySpecs.cs @@ -1217,6 +1217,46 @@ public void When_asserting_equivalence_of_non_generic_collections_it_should_resp .WithMessage("*Wheels*not have*VehicleId*not have*"); } + [Fact] + public void When_comparing_against_a_non_generic_collection_it_should_treat_it_as_unordered_collection_of_objects() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + List actual = new List { typeof(int), typeof(string) }; + IEnumerable expectation = new List { typeof(string), typeof(int) }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => actual.Should().BeEquivalentTo(expectation); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().NotThrow(); + } + + [Fact] + public void When_comparing_against_a_non_generic_collection_it_should_treat_it_as_collection_of_objects() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + List actual = new List { typeof(int), typeof(string) }; + IEnumerable expectation = new List { typeof(string), typeof(int) }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => actual.Should().BeEquivalentTo(expectation, o => o.WithStrictOrdering().WithTracing()); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().NotThrow(); + } + [Fact] public void When_custom_assertion_rules_are_utilized_the_rules_should_be_respected() {