Skip to content

Commit

Permalink
Added AllSatisfy for asserting all items in a collection satisfy an…
Browse files Browse the repository at this point in the history
… inspector (#1790)
  • Loading branch information
kmusick committed Jan 30, 2022
1 parent 6bb4d46 commit 6846f7f
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 1 deletion.
60 changes: 60 additions & 0 deletions Src/FluentAssertions/Collections/GenericCollectionAssertions.cs
Expand Up @@ -2671,6 +2671,66 @@ public AndConstraint<TAssertions> OnlyHaveUniqueItems(string because = "", param
return new AndConstraint<TAssertions>((TAssertions)this);
}

/// <summary>
/// Asserts that a collection contains only items which meet
/// the criteria provided by the inspector.
/// </summary>
/// <param name="expected">
/// The element inspector, which inspects each element in turn.
/// </param>
/// <param name="because">
/// A 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 <paramref name="because"/>.
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="expected"/> is <c>null</c>.</exception>
public AndConstraint<TAssertions> AllSatisfy(Action<T> expected, string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot verify against a <null> inspector");

bool success = Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected {context:collection} to contain only items satisfying the inspector{reason}, ")
.Given(() => Subject)
.ForCondition(subject => subject is not null)
.FailWith("but collection is <null>.")
.Then
.ForCondition(subject => subject.Any())
.FailWith("but collection is empty.")
.Then
.ClearExpectation();

if (success)
{
string[] failuresFromInspectors;

using (CallerIdentifier.OverrideStackSearchUsingCurrentScope())
{
var elementInspectors = Subject.Select(_ => expected);
failuresFromInspectors = CollectFailuresFromInspectors(elementInspectors);
}

if (failuresFromInspectors.Any())
{
string failureMessage = Environment.NewLine
+ string.Join(Environment.NewLine, failuresFromInspectors.Select(x => x.IndentLines()));

Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected {context:collection} to contain only items satisfying the inspector{reason}:")
.FailWithPreFormatted(failureMessage)
.Then
.ClearExpectation();
}

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

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

/// <summary>
/// Asserts that a collection contains exactly a given number of elements, which meet
/// the criteria provided by the element inspectors.
Expand Down
Expand Up @@ -392,6 +392,7 @@ namespace FluentAssertions.Collections
public FluentAssertions.AndConstraint<TAssertions> AllBeEquivalentTo<TExpectation>(TExpectation expectation, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> AllBeOfType(System.Type expectedType, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<TAssertions, System.Collections.Generic.IEnumerable<TExpectation>> AllBeOfType<TExpectation>(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> AllSatisfy(System.Action<T> expected, string because = "", params object[] becauseArgs) { }
protected void AssertCollectionEndsWith<TActual, TExpectation>(System.Collections.Generic.IEnumerable<TActual> actual, System.Collections.Generic.ICollection<TExpectation> expected, System.Func<TActual, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
protected void AssertCollectionStartsWith<TActual, TExpectation>(System.Collections.Generic.IEnumerable<TActual> actualItems, System.Collections.Generic.ICollection<TExpectation> expected, System.Func<TActual, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
protected void AssertSubjectEquality<TExpectation>(System.Collections.Generic.IEnumerable<TExpectation> expectation, System.Func<T, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -392,6 +392,7 @@ namespace FluentAssertions.Collections
public FluentAssertions.AndConstraint<TAssertions> AllBeEquivalentTo<TExpectation>(TExpectation expectation, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> AllBeOfType(System.Type expectedType, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<TAssertions, System.Collections.Generic.IEnumerable<TExpectation>> AllBeOfType<TExpectation>(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> AllSatisfy(System.Action<T> expected, string because = "", params object[] becauseArgs) { }
protected void AssertCollectionEndsWith<TActual, TExpectation>(System.Collections.Generic.IEnumerable<TActual> actual, System.Collections.Generic.ICollection<TExpectation> expected, System.Func<TActual, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
protected void AssertCollectionStartsWith<TActual, TExpectation>(System.Collections.Generic.IEnumerable<TActual> actualItems, System.Collections.Generic.ICollection<TExpectation> expected, System.Func<TActual, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
protected void AssertSubjectEquality<TExpectation>(System.Collections.Generic.IEnumerable<TExpectation> expectation, System.Func<T, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -392,6 +392,7 @@ namespace FluentAssertions.Collections
public FluentAssertions.AndConstraint<TAssertions> AllBeEquivalentTo<TExpectation>(TExpectation expectation, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> AllBeOfType(System.Type expectedType, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<TAssertions, System.Collections.Generic.IEnumerable<TExpectation>> AllBeOfType<TExpectation>(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> AllSatisfy(System.Action<T> expected, string because = "", params object[] becauseArgs) { }
protected void AssertCollectionEndsWith<TActual, TExpectation>(System.Collections.Generic.IEnumerable<TActual> actual, System.Collections.Generic.ICollection<TExpectation> expected, System.Func<TActual, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
protected void AssertCollectionStartsWith<TActual, TExpectation>(System.Collections.Generic.IEnumerable<TActual> actualItems, System.Collections.Generic.ICollection<TExpectation> expected, System.Func<TActual, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
protected void AssertSubjectEquality<TExpectation>(System.Collections.Generic.IEnumerable<TExpectation> expectation, System.Func<T, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -385,6 +385,7 @@ namespace FluentAssertions.Collections
public FluentAssertions.AndConstraint<TAssertions> AllBeEquivalentTo<TExpectation>(TExpectation expectation, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> AllBeOfType(System.Type expectedType, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<TAssertions, System.Collections.Generic.IEnumerable<TExpectation>> AllBeOfType<TExpectation>(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> AllSatisfy(System.Action<T> expected, string because = "", params object[] becauseArgs) { }
protected void AssertCollectionEndsWith<TActual, TExpectation>(System.Collections.Generic.IEnumerable<TActual> actual, System.Collections.Generic.ICollection<TExpectation> expected, System.Func<TActual, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
protected void AssertCollectionStartsWith<TActual, TExpectation>(System.Collections.Generic.IEnumerable<TActual> actualItems, System.Collections.Generic.ICollection<TExpectation> expected, System.Func<TActual, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
protected void AssertSubjectEquality<TExpectation>(System.Collections.Generic.IEnumerable<TExpectation> expectation, System.Func<T, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -392,6 +392,7 @@ namespace FluentAssertions.Collections
public FluentAssertions.AndConstraint<TAssertions> AllBeEquivalentTo<TExpectation>(TExpectation expectation, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> AllBeOfType(System.Type expectedType, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<TAssertions, System.Collections.Generic.IEnumerable<TExpectation>> AllBeOfType<TExpectation>(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> AllSatisfy(System.Action<T> expected, string because = "", params object[] becauseArgs) { }
protected void AssertCollectionEndsWith<TActual, TExpectation>(System.Collections.Generic.IEnumerable<TActual> actual, System.Collections.Generic.ICollection<TExpectation> expected, System.Func<TActual, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
protected void AssertCollectionStartsWith<TActual, TExpectation>(System.Collections.Generic.IEnumerable<TActual> actualItems, System.Collections.Generic.ICollection<TExpectation> expected, System.Func<TActual, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
protected void AssertSubjectEquality<TExpectation>(System.Collections.Generic.IEnumerable<TExpectation> expectation, System.Func<T, TExpectation, bool> equalityComparison, string because = "", params object[] becauseArgs) { }
Expand Down
@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions.Execution;
using Xunit;
using Xunit.Sdk;

namespace FluentAssertions.Specs.Collections
{
/// <content>
/// The AllSatisfy specs.
/// </content>
public partial class CollectionAssertionSpecs
{
public class AllSatisfy
{
[Fact]
public void A_null_inspector_should_throw()
{
// Arrange
IEnumerable<int> collection = new[] { 1, 2 };

// Act
Action act = () => collection.Should().AllSatisfy(null);

// Assert
act.Should()
.Throw<ArgumentException>()
.WithMessage("Cannot verify against a <null> inspector*");
}

[Fact]
public void A_null_collection_should_throw()
{
// Arrange
IEnumerable<int> collection = null;

// Act
Action act = () =>
{
using var _ = new AssertionScope();
collection.Should().AllSatisfy(x => x.Should().Be(1), "because we want to test the failure {0}", "message");
};

// Assert
act.Should()
.Throw<XunitException>()
.WithMessage(
"Expected collection to contain only items satisfying the inspector because we want to test the failure message, but collection is <null>.");
}

[Fact]
public void An_empty_collection_should_throw()
{
// Arrange
var collection = Enumerable.Empty<int>();

// Act
Action act = ()
=> collection.Should()
.AllSatisfy(x => x.Should().Be(1), "because we want to test the failure {0}", "message");

// Assert
act.Should()
.Throw<XunitException>()
.WithMessage(
"Expected collection to contain only items satisfying the inspector because we want to test the failure message, but collection is empty.");
}

[Fact]
public void All_items_satisfying_inspector_should_succeed()
{
// Arrange
var collection = new[] { new Customer { Age = 21, Name = "John" }, new Customer { Age = 21, Name = "Jane" } };

// Act / Assert
collection.Should().AllSatisfy(x => x.Age.Should().Be(21));
}

[Fact]
public void Any_items_not_satisfying_inspector_should_throw()
{
// Arrange
var customers = new[]
{
new CustomerWithItems { Age = 21, Items = new[] { 1, 2 } },
new CustomerWithItems { Age = 22, Items = new[] { 3 } }
};

// Act
Action act = () => customers.Should()
.AllSatisfy(
customer =>
{
customer.Age.Should().BeLessThan(21);
customer.Items.Should()
.AllSatisfy(item => item.Should().Be(3));
},
"because we want to test {0}",
"nested assertions");

// Assert
act.Should()
.Throw<XunitException>()
.WithMessage(
@"Expected customers to contain only items satisfying the inspector because we want to test nested assertions:
*At index 0:
*Expected customer.Age to be less than 21, but found 21
*Expected customer.Items to contain only items satisfying the inspector:
*At index 0:
*Expected item to be 3, but found 1
*At index 1:
*Expected item to be 3, but found 2
*At index 1:
*Expected customer.Age to be less than 21, but found 22"
);
}

[Fact]
public void Inspector_message_that_is_not_reformatable_should_not_throw()
{
// Arrange
byte[][] subject = { new byte[] { 1 } };

// Act
Action act = () => subject.Should().AllSatisfy(e => e.Should().BeEquivalentTo(new byte[] { 2, 3, 4 }));

// Assert
act.Should().NotThrow<FormatException>();
}
}
}
}
@@ -0,0 +1,44 @@
using System;
using Xunit;
using Xunit.Sdk;

namespace FluentAssertions.Specs.Collections
{
public partial class GenericCollectionAssertionOfStringSpecs
{
public class AllSatisfy
{
[Fact]
public void All_items_satisfying_inspector_should_succeed()
{
// Arrange
string[] collection = new[] { "John", "John" };

// Act / Assert
collection.Should().AllSatisfy(value => value.Should().Be("John"));
}

[Fact]
public void Any_items_not_satisfying_inspector_should_throw()
{
// Arrange
string[] collection = new[] { "Jack", "Jessica" };

// Act
Action act = () => collection.Should()
.AllSatisfy(
value => value.Should().Be("John"),
"because we want to test the failure {0}",
"message");

// Assert
act.Should()
.Throw<XunitException>()
.WithMessage(
"Expected collection to contain only items satisfying the inspector because we want to test the failure message:"
+ "*John*Jack"
+ "*John*Jessica*");
}
}
}
}
Expand Up @@ -9,7 +9,7 @@

namespace FluentAssertions.Specs.Collections
{
public class GenericCollectionAssertionOfStringSpecs
public partial class GenericCollectionAssertionOfStringSpecs
{
[Fact]
public void Should_fail_when_asserting_collection_has_a_count_that_is_different_from_the_number_of_items()
Expand Down
16 changes: 16 additions & 0 deletions docs/_pages/collections.md
Expand Up @@ -190,6 +190,22 @@ collection.Should().SatisfyRespectively(
});
```

If you need to perform the same assertion on all elements of a collection:

```csharp
var collection = new []
{
new { Id = 1, Name = "John", Attributes = new string[] { } },
new { Id = 2, Name = "Jane", Attributes = new string[] { "attr" } }
};
collection.Should().AllSatisfy(x =>
{
x.Id.Should().BePositive();
x.Name.Should().StartWith("J");
x.Attributes.Should().NotBeNull();
});
```

If you need to perform individual assertions on all elements of a collection without setting expectation about the order of elements:

```csharp
Expand Down
1 change: 1 addition & 0 deletions docs/_pages/releases.md
Expand Up @@ -10,6 +10,7 @@ sidebar:
## Unreleased

### What's New
* Added `AllSatisfy` for asserting all items in a collection satisfy an inspector - [#1790](https://github.com/fluentassertions/fluentassertions/pull/1790)
* Added `WithMapping` option to `BeEquivalentTo` to map members with different names between the subject and expectation - [#1742](https://github.com/fluentassertions/fluentassertions/pull/1742)

### Fixes
Expand Down

0 comments on commit 6846f7f

Please sign in to comment.