diff --git a/Src/FluentAssertions/Equivalency/OrderingRuleCollection.cs b/Src/FluentAssertions/Equivalency/OrderingRuleCollection.cs index 94c8111907..66fabdecd4 100644 --- a/Src/FluentAssertions/Equivalency/OrderingRuleCollection.cs +++ b/Src/FluentAssertions/Equivalency/OrderingRuleCollection.cs @@ -56,6 +56,11 @@ public void Add(IOrderingRule rule) rules.Add(rule); } + internal void Clear() + { + rules.Clear(); + } + /// /// Determines whether the rules in this collection dictate strict ordering during the equivalency assertion on /// the collection pointed to by . diff --git a/Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyAssertionOptions.cs b/Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyAssertionOptions.cs index 646bd35359..b121697f7f 100644 --- a/Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyAssertionOptions.cs +++ b/Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyAssertionOptions.cs @@ -435,6 +435,7 @@ public TSelf Using(IEquivalencyStep equivalencyStep) /// public TSelf WithStrictOrdering() { + orderingRules.Clear(); orderingRules.Add(new MatchAllOrderingRule()); return (TSelf)this; } @@ -449,6 +450,16 @@ public TSelf WithStrictOrderingFor(Expression> predicate return (TSelf)this; } + /// + /// Causes all collections - except bytes - to be compared ignoring the order in which the items appear in the expectation. + /// + public TSelf WithoutStrictOrdering() + { + orderingRules.Clear(); + orderingRules.Add(new ByteArrayOrderingRule()); + return (TSelf)this; + } + /// /// Causes the collection identified by the provided to be compared ignoring the order /// in which the items appear in the expectation. diff --git a/Tests/Shared.Specs/CollectionEquivalencySpecs.cs b/Tests/Shared.Specs/CollectionEquivalencySpecs.cs index cdbce1a0af..42bdeeec58 100644 --- a/Tests/Shared.Specs/CollectionEquivalencySpecs.cs +++ b/Tests/Shared.Specs/CollectionEquivalencySpecs.cs @@ -302,6 +302,56 @@ public void When_a_byte_array_does_not_match_strictly_it_should_throw() .WithMessage("Expected*item[0]*6*1*"); } + [Fact] + public void When_a_byte_array_does_not_match_strictly_and_order_is_not_strict_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var subject = new byte[] { 1, 2, 3, 4, 5, 6 }; + + var expectation = new byte[] { 6, 5, 4, 3, 2, 1 }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action action = () => subject.Should().BeEquivalentTo(expectation, options => options.WithoutStrictOrdering()); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + action.Should().Throw() + .WithMessage("Expected*item[0]*6*1*"); + } + + [Fact] + public void When_a_collection_property_is_a_byte_array_which_does_not_match_strictly_and_order_is_not_strict_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var subject = new + { + bytes = new byte[] { 1, 2, 3, 4, 5, 6 } + }; + + var expectation = new + { + bytes = new byte[] { 6, 5, 4, 3, 2, 1 } + }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action action = () => subject.Should().BeEquivalentTo(expectation, options => options.WithoutStrictOrdering()); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + action.Should().Throw() + .WithMessage("Expected *member bytes[0]*6*1*"); + } + [Fact] public void When_a_collection_does_not_match_it_should_include_items_in_message() { @@ -1094,6 +1144,54 @@ public void When_an_unordered_collection_must_be_strict_using_a_predicate_it_sho .WithMessage("*Expected item[0].UnorderedCollection*5 item(s)*empty collection*"); } + [Fact] + public void When_an_unordered_collection_must_be_strict_using_a_predicate_and_order_was_reset_to_not_strict_it_should_not_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var subject = new[] + { + new + { + Name = "John", + UnorderedCollection = new[] { 1, 2, 3, 4, 5 } + }, + new + { + Name = "Jane", + UnorderedCollection = new int[0] + } + }; + + var expectation = new[] + { + new + { + Name = "John", + UnorderedCollection = new[] { 5, 4, 3, 2, 1 } + }, + new + { + Name = "Jane", + UnorderedCollection = new int[0] + } + }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action action = () => subject.Should().BeEquivalentTo(expectation, options => + options + .WithStrictOrderingFor(s => s.SelectedMemberPath.Contains("UnorderedCollection")) + .WithoutStrictOrdering()); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + action.Should().NotThrow(); + } + [Fact] public void When_an_unordered_collection_must_be_strict_using_an_expression_it_should_throw() { @@ -1146,6 +1244,56 @@ public void When_an_unordered_collection_must_be_strict_using_an_expression_it_s "*Expected item[0].UnorderedCollection*5 item(s)*empty collection*"); } + [Fact] + public void When_an_unordered_collection_must_be_strict_using_an_expression_and_order_is_reset_to_not_strict_it_should_not_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var subject = new[] + { + new + { + Name = "John", + UnorderedCollection = new[] { 1, 2, 3, 4, 5 } + }, + new + { + Name = "Jane", + UnorderedCollection = new int[0] + } + }; + + var expectation = new[] + { + new + { + Name = "John", + UnorderedCollection = new[] { 5, 4, 3, 2, 1 } + }, + new + { + Name = "Jane", + UnorderedCollection = new int[0] + } + }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action action = + () => + subject.Should().BeEquivalentTo(expectation, + options => options + .WithStrictOrderingFor(s => s.UnorderedCollection) + .WithoutStrictOrdering()); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + action.Should().NotThrow(); + } + [Fact] public void When_an_unordered_collection_must_not_be_strict_using_a_predicate_it_should_not_throw() { @@ -1193,6 +1341,56 @@ public void When_an_unordered_collection_must_not_be_strict_using_a_predicate_it action.Should().NotThrow(); } + [Fact] + public void When_an_unordered_collection_must_not_be_strict_using_a_predicate_and_order_was_reset_to_strict_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var subject = new[] + { + new + { + Name = "John", + UnorderedCollection = new[] { 1, 2 } + }, + new + { + Name = "Jane", + UnorderedCollection = new int[0] + } + }; + + var expectation = new[] + { + new + { + Name = "John", + UnorderedCollection = new[] { 2, 1 } + }, + new + { + Name = "Jane", + UnorderedCollection = new int[0] + } + }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action action = () => subject.Should().BeEquivalentTo(expectation, options => options + .WithStrictOrdering() + .WithoutStrictOrderingFor(s => s.SelectedMemberPath.Contains("UnorderedCollection")) + .WithStrictOrdering()); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + action.Should().Throw() + .WithMessage( + "*Expected item[0].UnorderedCollection[0] to be 2, but found 1.*Expected item[0].UnorderedCollection[1] to be 1, but found 2*"); + } + [Fact] public void When_asserting_equivalence_of_collections_and_configured_to_use_runtime_properties_it_should_respect_the_runtime_type @@ -2584,6 +2782,114 @@ public void When_two_unordered_lists_are_structurally_equivalent_and_order_is_st "Expected item[0].Name*Jane*John*item[1].Name*John*Jane*"); } + [Fact] + public void When_two_unordered_lists_are_structurally_equivalent_and_order_was_reset_to_strict_it_should_fail() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var subject = new[] + { + new Customer + { + Name = "John", + Age = 27, + Id = 1 + }, + new Customer + { + Name = "Jane", + Age = 24, + Id = 2 + } + }; + + var expectation = new Collection + { + new Customer + { + Name = "Jane", + Age = 24, + Id = 2 + }, + new Customer + { + Name = "John", + Age = 27, + Id = 1 + } + }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action action = () => subject.Should().BeEquivalentTo( + expectation, + options => options + .WithStrictOrdering() + .WithoutStrictOrdering() + .WithStrictOrdering()); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + action.Should().Throw() + .WithMessage( + "Expected item[0].Name*Jane*John*item[1].Name*John*Jane*"); + } + + [Fact] + public void + When_two_unordered_lists_are_structurally_equivalent_and_order_was_reset_to_not_strict_it_should_succeed + () + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var subject = new[] + { + new Customer + { + Name = "John", + Age = 27, + Id = 1 + }, + new Customer + { + Name = "Jane", + Age = 24, + Id = 2 + } + }; + + var expectation = new Collection + { + new Customer + { + Name = "Jane", + Age = 24, + Id = 2 + }, + new Customer + { + Name = "John", + Age = 27, + Id = 1 + } + }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action action = + () => subject.Should().BeEquivalentTo(expectation, x => x.WithStrictOrdering().WithoutStrictOrdering()); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + action.Should().NotThrow(); + } + [Fact] public void When_two_unordered_lists_are_structurally_equivalent_it_should_succeed diff --git a/docs/_pages/documentation.md b/docs/_pages/documentation.md index 9f873b0edc..223ff679cb 100644 --- a/docs/_pages/documentation.md +++ b/docs/_pages/documentation.md @@ -926,7 +926,20 @@ You can even tell FA to use strict ordering only for a particular collection or orderDto.Should().BeEquivalentTo(expectation, options => options.WithStrictOrderingFor(s => s.Products)); ``` -**Notice:** For performance reasons, collections of bytes are compared in exact order. +And you can tell FA to generally use strict ordering but ignore it for a particular collection or dictionary member: + +```csharp +orderDto.Should().BeEquivalentTo(expectation, options => options.WithStrictOrdering().WithoutStrictOrderingFor(s => s.Products)); +``` + +In case you chose to use strict ordering by default you can still configure non-strict ordering in specific tests: +```csharp +AssertionOptions.AssertEquivalencyUsing(options => options.WithStrictOrdering()); + +orderDto.Should().BeEquivalentTo(expectation, options => options.WithoutStrictOrdering()); +``` + +**Notice:** For performance reasons, collections of bytes are compared in exact order. This is even true when applying `WithoutStrictOrdering()`. ### Diagnostics `Should().BeEquivalentTo` is a very powerful feature, and one of the unique selling points of Fluent Assertions. But sometimes it can be a bit overwhelming, especially if some assertion fails under unexpected conditions. To help you understand how Fluent Assertions compared two (collections of) object graphs, the failure message will always include the relevant configuration settings: