Repository navigation
[API Proposal]: Advanced string comparison #2720
Description
Background and motivation
I love most of string methods implemented in FluentAssertions, most of these are pretty obvious:
theString.Should().NotBeNull();
theString.Should().BeNull();
theString.Should().BeEmpty();
theString.Should().NotBeEmpty("because the string is not empty");
theString.Should().HaveLength(0);
theString.Should().BeNullOrWhiteSpace(); // either null, empty or whitespace only
theString.Should().NotBeNullOrWhiteSpace();
theString.Should().Be("This is a String");
theString.Should().NotBe("This is another String");
However what happens when you want to check if string is case invariant?
theString.Should().BeEquivalentTo("THIS IS A STRING");
At first glance, this method does anything. Does it trim whitespace? Maybe. Does it take casing into account? Maybe. Does it behave like object.Should().BeEquvalentTo()? Actually not.
This forces reader to reach for the documentation, and while this is quite easy in IDE, it can be improved.
This story could be greatly improved by making api similar to builtin comparison apis, which are provided by framework, i.e. string.Compare, string.Contains or string.StartsWith and similar methods.
Microsoft suggests specifying intent when comparing strings, and this perfectly applies to tests as well.
API Proposal
public class StringAssertions<TAssertions>
{
public AndConstraint<TAssertions> Be(string expected, StringComparison comparisonType, string because = "", params object[] becauseArgs);
public AndConstraint<TAssertions> Be(string expected, CultureInfo? cultureInfo, CompareOptions compareOptions, string because = "", params object[] becauseArgs); // This could be potentially usefull to handle cultures like Turkish, however I do not know if this is really useful api
public AndConstraint<TAssertions> BeOrdinal(string expected, string because = "", params object[] becauseArgs); // Or BeIgnoreCase
}API Usage
var theString = "abc";
theString.Should().BeOrdinal("ABC");
theString.Should().Be("ABC", StringComparison.IgnoreCase);
theString.Should().Be("abc", CultureInfo.CurrentCulture);Alternative Designs
No response
Risks
There will be multiple ways of asserting string equality:
"abc".Should().BeEquivalentTo("ABC");
"abc".Should().Be("ABC", StringComparison.IgnoreCase);
"abc".Should().BeOrdinal("ABC");
This is not huge problem. It may be a bit confusing in case multiple methods are used in single codebase.
Possible mitigations:
- Do nothing, it's not huge problem
- Mark
StringAssertions.BeEquivalentToas Obsolete (however this is huge change for existing costumers) - Implement some kind of code analyzer which checks for presence of both methods in single codebase and proposes single
Are you willing to help with a proof-of-concept (as PR in that or a separate repo) first and as pull-request later on?
Yes, please assign this issue to me.
Activity
dennisdoomen commentedon Aug 10, 2024
I'm not against it, but we should make this a complete example of all the methods and overloads you would like to add, before we approve the proposal.
Notice that in #2413 we introduced extra options to
BeEquivalentToand extra overloads to some of the other string assertions that take anEquivalencyOptionsparameter.Xceed-DelvaJB commentedon May 19, 2026
Function list
In the
StringAssertionsclass, we could addSubstring comparison
The .NET framework offers substring comparison with
Users can already assert substrings by manually calling String.SubString() or ocnverting to a ReadOnlySpan (but not available in .NET 4.7).
We could add assertions to
StringAssertionsto offer convenience or for when the actual and/or expected strings are very large.The implementations would call
BeEquivalentTo()with an appropriately builtEquivalencyOptions<string>object.It needs APIs to store the new parameters.
Summary and questions
I've listed a possible list of methods to add to
StringAssertionsthat cover string comparison options available in the .NET Framework.They would be shortcuts to calling
BeEquivalentTo()with existing and new options.Do we want to cover all these options? I would say why not? It would feel more natural to use these methods than to build
EquivalencyOptions<string>objects.I see that
IEquivalencyOptions/SelfReferenceEquivalencyOptionsrepresents data that is not very closely related.But is adding the string comparison options, especially the indexes going too far?
And if it's a viable design, I'm not sure what does in IEquivalencyOptions vs SelfReferenceEquivalencyOptions.