Skip to content

Commit

Permalink
add collection assertion ContainEquivalentTo
Browse files Browse the repository at this point in the history
asserts that a collection contains at least one object that is equivalent to the expected object.
  • Loading branch information
matthiaslischka committed Oct 18, 2018
1 parent 7be38a2 commit c8dd3a0
Show file tree
Hide file tree
Showing 3 changed files with 316 additions and 1 deletion.
88 changes: 88 additions & 0 deletions Src/FluentAssertions/Collections/CollectionAssertions.cs
Expand Up @@ -462,6 +462,94 @@ public AndConstraint<TAssertions> BeEquivalentTo(params object[] expectations)
return new AndConstraint<TAssertions>((TAssertions)this);
}

/// <summary>
/// Asserts that a collection of objects contains an object equivalent to another object.
/// </summary>
/// <remarks>
/// 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 <see cref="AssertionOptions"/>.
/// </remarks>
/// <param name="because">
/// An optional formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the
/// assertion is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <see cref="because" />.
/// </param>
public AndConstraint<TAssertions> ContainEquivalentTo<TExpectation>(TExpectation expectation, string because = "",
params object[] becauseArgs)
{
return ContainEquivalentTo(expectation, config => config, because, becauseArgs);
}

/// <summary>
/// Asserts that a collection of objects contains an object equivalent to another object.
/// </summary>
/// <remarks>
/// 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 <see cref="AssertionOptions"/>.
/// </remarks>
/// <param name="config">
/// A reference to the <see cref="EquivalencyAssertionOptions{TSubject}"/> configuration object that can be used
/// to influence the way the object graphs are compared. You can also provide an alternative instance of the
/// <see cref="EquivalencyAssertionOptions{TSubject}"/> class. The global defaults are determined by the
/// <see cref="AssertionOptions"/> class.
/// </param>
/// <param name="because">
/// An optional formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the
/// assertion is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <see cref="because" />.
/// </param>
public AndConstraint<TAssertions> ContainEquivalentTo<TExpectation>(TExpectation expectation, Func<EquivalencyAssertionOptions<TExpectation>,
EquivalencyAssertionOptions<TExpectation>> 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 <null>.", expectation);
}

IEquivalencyAssertionOptions options = config(AssertionOptions.CloneDefaults<TExpectation>());
IEnumerable<object> actualItems = Subject.Cast<object>();

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>((TAssertions)this);
}
}

Execute.Assertion
.BecauseOf(because, becauseArgs)
.FailWith("Expected collection {0} to contain equivalent to {1}.", Subject, expectation);

return new AndConstraint<TAssertions>((TAssertions)this);
}

/// <summary>
/// Asserts that the current collection only contains items that are assignable to the type <typeparamref name="T" />.
/// </summary>
Expand Down
226 changes: 225 additions & 1 deletion 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;
Expand Down Expand Up @@ -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<XunitException>().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<XunitException>().WithMessage("Expected collection to contain equivalent to 1 because we want to test the behaviour with a null subject, but found <null>.");
}

[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<XunitException>().WithMessage("Expected collection {1, 2, 3} to contain equivalent to <null>.");
}

[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<XunitException>().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<XunitException>();
}

[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]
Expand Down
3 changes: 3 additions & 0 deletions docs/_pages/documentation.md
Expand Up @@ -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);
Expand Down

0 comments on commit c8dd3a0

Please sign in to comment.