Skip to content

New string-specific options for string equivalency assertions #2364

@vbreuss

Description

@vbreuss

Background and motivation

Initial discussion in #2339 and #2064

I would like to add some overloads to most string equivalency assertions to tackle various use cases (e.g. ignore white space, ignore casing, ignore newline, etc.) with the goal to tackle most existing use cases and to allow for extensibility of the provided mechanism. In order to achieve this, I would add an overload with a Func<EquivalencyAssertionOptions<string>, EquivalencyAssertionOptions<string>> to specify additional options on the string comparison like ignoring leading or trailing white space or newlines to the following string assertions:

  • BeEquivalentTo
  • ContainEquivalentOf
  • EndWithEquivalentOf
  • MatchEquivalentOf
  • NotBeEquivalentTo
  • NotContainEquivalentOf
  • NotEndWithEquivalentOf
  • NotMatchEquivalentOf
  • NotStartWithEquivalentOf
  • StartWithEquivalentOf

This also allows specifying an IEqualityComparer<string> as requested in #2339 via the .Using method on the assertion options.

API Proposal

Example for BeEquivalentTo:

    public AndConstraint<TAssertions> BeEquivalentTo(string expected,
        Func<EquivalencyAssertionOptions<string>, EquivalencyAssertionOptions<string>> config,
        string because = "", params object[] becauseArgs)
    {
      // ...
    }

    public AndConstraint<TAssertions> NotBeEquivalentTo(string unexpected,
        Func<EquivalencyAssertionOptions<string>, EquivalencyAssertionOptions<string>> config,
        string because = "", params object[] becauseArgs)
    {
      // ...
    }

and add the following methods to the EquivalencyAssertionOptions<T> class:

    public EquivalencyAssertionOptions<T> IgnoringLeadingWhitespace()
    {
        // ...
        return this;
    }

    public EquivalencyAssertionOptions<T> IgnoringTrailingWhitespace()
    {
        // ...
        return this;
    }

    public EquivalencyAssertionOptions<T> IgnoringCase()
    {
        // ...
        return this;
    }

API Usage

subject.Should().BeEquivalentTo(expected, o => o
    .IgnoringLeadingWhitespace()
    .IgnoringTrailingWhitespace()
    .IgnoringCase());
subject.Should().BeEquivalentTo(expected, o => o
    .Using(StringComparer.InvariantCultureIgnoreCase));

Alternative Designs

No response

Risks

As I would support arbitrary IEqualityComparer<string>, I can no longer use the IndexOfFirstMismatch extension method, but would have to rewrite it, e.g. as follows:

    public static int IndexOfFirstMismatch(this string value, string expected, IEqualityComparer<string> comparer)
    {
        for (int index = 0; index < value.Length; index++)
        {
            if (index >= expected.Length || !comparer.Equals(value[index..(index + 1)], expected[index..(index + 1)]))
            {
                return index;
            }
        }

        return -1;
    }

The difference is, that I have to slice the strings and get substrings, instead of comparing char by char. This could have a performance impact...

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved, it can be implemented

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions