Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NotContainEquivalentOf with OccurrenceConstraint parameter for string assertions #2436

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 48 additions & 0 deletions Src/FluentAssertions/Primitives/StringAssertions.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
Expand Down Expand Up @@ -1260,6 +1261,53 @@ public AndConstraint<TAssertions> NotContainAny(params string[] values)
return new AndConstraint<TAssertions>((TAssertions)this);
}

/// <summary>
/// Asserts that a string does not contain the specified <paramref name="unexpected"/> string,
/// including any leading or trailing whitespace, with the exception of the casing.
/// </summary>
/// <param name="unexpected">The string that the subject is not expected to contain.</param>
/// <param name="occurrenceConstraint">
/// A constraint specifying the amount of times a substring should be present within the test subject.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 This isn't clear for me. How does the occurrence work in combination with "should not contain equivalent of"?

/// It can be created by invoking static methods Once, Twice, Thrice, or Times(int)
/// on the classes <see cref="Exactly"/>, <see cref="AtLeast"/>, <see cref="MoreThan"/>, <see cref="AtMost"/>, and <see cref="LessThan"/>.
/// For example, <see cref="Exactly.Times(int)"/> or <see cref="LessThan.Twice()"/>.
/// </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>
public AndConstraint<TAssertions> NotContainEquivalentOf(string unexpected,
OccurrenceConstraint occurrenceConstraint,
string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(occurrenceConstraint);

Execute.Assertion
.ForCondition(!string.IsNullOrEmpty(unexpected) && Subject != null)
.BecauseOf(because, becauseArgs)
.FailWith("Did not expect {context:string} to contain the equivalent of {0}{reason}, but found {1}.", unexpected, Subject);

bool notEquivalent;

using (var scope = new AssertionScope())
{
Subject.Should().ContainEquivalentOf(unexpected, occurrenceConstraint);
notEquivalent = scope.Discard().Length > 0;
}

var assertion = Execute.Assertion;
occurrenceConstraint.RegisterReportables(assertion);
assertion
.ForCondition(notEquivalent)
.BecauseOf(because, becauseArgs)
.FailWith("Did not expect {context:string} to contain the equivalent of {0} {expectedOccurrence}{reason}, but found it in {1}.", unexpected, Subject);

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

private static bool Contains(string actual, string expected, StringComparison comparison)
{
return (actual ?? string.Empty).Contains(expected ?? string.Empty, comparison);
Expand Down
Expand Up @@ -2060,6 +2060,7 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> NotContainAny(params string[] values) { }
public FluentAssertions.AndConstraint<TAssertions> NotContainAny(System.Collections.Generic.IEnumerable<string> values, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotContainEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotContainEquivalentOf(string unexpected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotEndWith(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotEndWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -2144,6 +2144,7 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> NotContainAny(params string[] values) { }
public FluentAssertions.AndConstraint<TAssertions> NotContainAny(System.Collections.Generic.IEnumerable<string> values, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotContainEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotContainEquivalentOf(string unexpected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotEndWith(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotEndWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -2011,6 +2011,7 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> NotContainAny(params string[] values) { }
public FluentAssertions.AndConstraint<TAssertions> NotContainAny(System.Collections.Generic.IEnumerable<string> values, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotContainEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotContainEquivalentOf(string unexpected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotEndWith(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotEndWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -2060,6 +2060,7 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> NotContainAny(params string[] values) { }
public FluentAssertions.AndConstraint<TAssertions> NotContainAny(System.Collections.Generic.IEnumerable<string> values, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotContainEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotContainEquivalentOf(string unexpected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotEndWith(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotEndWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -470,5 +470,24 @@ public void Should_succeed_when_asserting_string_does_not_contain_equivalent_of_
// Assert
act.Should().NotThrow();
}

[Fact]
public void Fail_for_too_many_occurences_according_to_occurence_constraint()
{
// Act
Action act = () =>
"abc".Should().NotContainEquivalentOf("a", AtMost.Once());

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Did not expect string to contain the equivalent of \"a\" at most 1 time, but found it in \"abc\".");
}

[Fact]
public void Succeed_for_correct_occurences_according_to_occurence_constraint()
{
// Act / Assert
"abc".Should().NotContainEquivalentOf("a", AtLeast.Twice());
}
}
}
1 change: 1 addition & 0 deletions docs/_pages/releases.md
Expand Up @@ -16,6 +16,7 @@ sidebar:
### Improvements
* Improve failure message for string assertions when checking for equality - [#2307](https://github.com/fluentassertions/fluentassertions/pull/2307)
* You can mark all assertions in an assembly as custom assertions using the `[CustomAssertionsAssembly]` attribute - [#2389](https://github.com/fluentassertions/fluentassertions/pull/2389)
* Add `NotContainEquivalentOf` with `OccurrenceConstraint` parameter for string assertions - [#2436](https://github.com/fluentassertions/fluentassertions/pull/2436)

### Fixes
* Fixed formatting error when checking nullable `DateTimeOffset` with
Expand Down