diff --git a/Src/FluentAssertions/Collections/CollectionAssertions.cs b/Src/FluentAssertions/Collections/CollectionAssertions.cs index 3f18f89836..34bd582912 100644 --- a/Src/FluentAssertions/Collections/CollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/CollectionAssertions.cs @@ -462,6 +462,94 @@ public AndConstraint BeEquivalentTo(params object[] expectations) return new AndConstraint((TAssertions)this); } + /// + /// Asserts that a collection of objects contains an object equivalent to another object. + /// + /// + /// Objects within the collection are equivalent to the expected object 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. + /// 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 AndConstraint ContainEquivalentTo(TExpectation expectation, string because = "", + params object[] becauseArgs) + { + return ContainEquivalentTo(expectation, config => config, because, becauseArgs); + } + + /// + /// Asserts that a collection of objects contains an object equivalent to another object. + /// + /// + /// Objects within the collection are equivalent to the expected object 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. + /// 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 ContainEquivalentTo(TExpectation expectation, Func, + EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) + { + if(ReferenceEquals(Subject, null)) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:collection} to contain equivalent to {0}{reason}, but found .", expectation); + } + + IEquivalencyAssertionOptions options = config(AssertionOptions.CloneDefaults()); + IEnumerable actualItems = Subject.Cast(); + + using (var scope = new AssertionScope()) + { + foreach (object actualItem in actualItems) + { + var context = new EquivalencyValidationContext + { + Subject = actualItem, + Expectation = expectation, + CompileTimeType = typeof(TExpectation), + Because = because, + BecauseArgs = becauseArgs, + Tracer = options.TraceWriter, + }; + + var equivalencyValidator = new EquivalencyValidator(options); + equivalencyValidator.AssertEquality(context); + + string[] failures = scope.Discard(); + + if (!failures.Any()) + return new AndConstraint((TAssertions)this); + } + } + + Execute.Assertion + .BecauseOf(because, becauseArgs) + .FailWith("Expected collection {0} to contain equivalent to {1}.", Subject, expectation); + + return new AndConstraint((TAssertions)this); + } + /// /// Asserts that the current collection only contains items that are assignable to the type . /// diff --git a/Tests/Shared.Specs/CollectionAssertionSpecs.cs b/Tests/Shared.Specs/CollectionAssertionSpecs.cs index b39402e930..30ab830782 100644 --- a/Tests/Shared.Specs/CollectionAssertionSpecs.cs +++ b/Tests/Shared.Specs/CollectionAssertionSpecs.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using Xunit; using Xunit.Sdk; @@ -2337,6 +2336,231 @@ public void When_testing_collections_not_to_be_equivalent_against_same_collectio #endregion + #region Contain Equivalent To + + [Fact] + public void When_collection_contains_object_equal_to_another_it_should_succeed() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + int item = 2; + IEnumerable collection = new[] { 1, item, 3 }; + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + collection.Should().ContainEquivalentTo(item); + } + + [Fact] + public void When_collection_contains_object_equivalent_to_another_it_should_succeed() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + IEnumerable collection = new[] { 1, 2, 3 }; + int item = 2; + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + collection.Should().ContainEquivalentTo(item); + } + + [Fact] + public void When_character_collection_does_contain_equivalent_it_should_succeed() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + char[] list = ("abc123ab").ToCharArray(); + char item = 'c'; + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + list.Should().ContainEquivalentTo(item); + } + + [Fact] + public void When_collection_does_not_contain_object_equivalent_to_another_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + IEnumerable collection = new[] { 1, 2, 3 }; + int item = 4; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => collection.Should().ContainEquivalentTo(item); ; + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw().WithMessage("Expected collection {1, 2, 3} to contain equivalent to 4."); + } + + [Fact] + public void When_asserting_collection_to_contain_equivalent_but_collection_is_null_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + IEnumerable actual = null; + int expectation = 1; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => + actual.Should().ContainEquivalentTo(expectation, "because we want to test the behaviour with a null subject"); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw().WithMessage("Expected collection to contain equivalent to 1 because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_collection_contains_equivalent_null_object_it_should_succeed() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + IEnumerable subject = new[] { 1, 2, 3, (int?)null }; + int? item = null; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => subject.Should().ContainEquivalentTo(item); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().NotThrow(); + } + + [Fact] + public void When_collection_does_not_contain_equivalent_null_object_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + IEnumerable collection = new[] { 1, 2, 3 }; + int? item = null; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => collection.Should().ContainEquivalentTo(item); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw().WithMessage("Expected collection {1, 2, 3} to contain equivalent to ."); + } + + [Fact] + public void When_empty_collection_does_not_contain_equivalent_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + IEnumerable subject = new int[0]; + int item = 1; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => subject.Should().ContainEquivalentTo(item); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw().WithMessage("Expected collection {empty} to contain equivalent to 1."); + } + + [Fact] + public void When_collection_does_not_contain_equivalent_because_of_second_property_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + IEnumerable subject = new [] + { + new Customer + { + Name = "John", + Age = 18 + }, + new Customer + { + Name = "Jane", + Age = 18 + } + }; + var item = new Customer { Name = "John", Age = 20 }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => subject.Should().ContainEquivalentTo(item); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw(); + } + + [Fact] + public void When_collection_does_contain_equivalent_by_including_single_property_it_should_not_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + IEnumerable subject = new [] + { + new Customer + { + Name = "John", + Age = 18 + }, + new Customer + { + Name = "Jane", + Age = 18 + } + }; + var item = new Customer { Name = "John", Age = 20 }; + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + subject.Should().ContainEquivalentTo(item, options => options.Including(x => x.Name)); + } + + [Fact] + public void When_collection_contains_object_equivalent_to_boxed_object_it_should_succeed() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + IEnumerable collection = new[] { 1, 2, 3 }; + object boxedValue = 2; + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + 2.Should().Equals(boxedValue); + collection.Should().ContainEquivalentTo(boxedValue); + } + + #endregion + #region Be Subset Of [Fact] diff --git a/docs/_pages/documentation.md b/docs/_pages/documentation.md index 9f873b0edc..d448ae1973 100644 --- a/docs/_pages/documentation.md +++ b/docs/_pages/documentation.md @@ -461,6 +461,9 @@ collection.Should().NotContain(new[] { 82, 83 }); collection.Should().NotContainNulls(); collection.Should().NotContain(x => x > 10); +object boxedValue = 2; +collection.Should().ContainEquivalentTo(boxedValue); // Compared by object equivalence + const int successor = 5; const int predecessor = 5; collection.Should().HaveElementPreceding(successor, element);