How can I compare null and string.Empty (or "") in fluent assertions?
Asked Answered
Y

2

5

I have two objects of the same type, the type has a string field, in the first object the value is null, in the second one the value is "", how can I force fluent assesrtions to assume that this is correct?

Assesrtion itself:

callResult.ShouldBeEquivalentTo(centreResponse, 
                                opt => opt.Excluding(r => r.DateOpen));

here the exception is raised, stating the the expected value is null, the real one is "" (or vice versa)

Yand answered 4/6, 2014 at 9:35 Comment(5)
What type are the objects? Can I assume that centre response is a string and opt is string[] or what?Hovel
Why would you want to treat null and the empty string as equivalent? That's rarely a good idea.Sputnik
The objects are of type CentreResponse, which is a complex type, about the rules of comparison - that's the requirement, they come from different data sources, but in this particular context null and empty string are equalYand
With field, do you mean a C# field?Saleable
I mean a string typed property, sorry for misleading :)Yand
S
10

What you can do is to override the behavior for properties of type string like this:

callResult.ShouldBeEquivalentTo(centreResponse, opt => opt
          .Excluding(r => r.DateOpen)
          .Using<string>(ctx => CompareStrings(ctx)).WhenTypeIs<string>());       

public void CompareStrings(IAssertionContext<string> ctx)
{
    var equal = (ctx.Subject ?? string.Empty).Equals(ctx.Expectation ?? string.Empty);

    Execute.Assertion
        .BecauseOf(ctx.Because, ctx.BecauseArgs)
        .ForCondition(equal)
        .FailWith("Expected {context:string} to be {0}{reason}, but found {1}", ctx.Subject, ctx.Expectation);
}    

You can clean this up a bit by encapsulating the CompareStrings method in an implementation of IAssertionRule. See the RelaxingDateTimeAssertionRule in the unit tests of FluentAssertions here.

You can add that custom assertion rule as the default for all assertions on your the type of your callResult variable, but I still have to add something to allow global defaults.

Saleable answered 6/6, 2014 at 13:54 Comment(0)
A
0

After looking for a while for a more up-to-date answer, here's the solution in 2024. It involves using IEquivalencyStep to implement a custom class to be referenced through the Using() method of the options of BeEquivalentTo().

In the following example, I'm testing with a Dictionary with some string key in it, in order to show that it works with types that include a string in them.

[Test]
public void StringNullOrEmpty()
{
    Dictionary<string, object> dict1 = new();
    Dictionary<string, object> dict2 = new();
    dict1["SomeString"] = null;
    dict2["SomeString"] = "";

    dict1.Should().BeEquivalentTo(dict2, opt => opt.Using(new NullStringEquivalencyStep()));
}

private class NullStringEquivalencyStep : IEquivalencyStep
{
    public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
    {
        if (comparands.GetExpectedType(context.Options) == typeof(string)
            && string.IsNullOrEmpty(comparands.Subject as string)
            && string.IsNullOrEmpty(comparands.Expectation as string))
        {
            return EquivalencyResult.AssertionCompleted;
        }

        return EquivalencyResult.ContinueWithNext;
    }
}
Ardolino answered 8/4 at 17:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.