Home > Software engineering >  FluentAssertions 6 ObjectGraph compare Enum to String
FluentAssertions 6 ObjectGraph compare Enum to String

Time:02-16

With FluentAssertions 6 it seems you can longer verify if in a object graph if an Enum is equivalent to a string. Source: https://fluentassertions.com/upgradingtov6

enum MyEnum {
   A,
   B
}

class Source {
   MyEnum Enum { get;set;}
}

class Expectation {
   string Enum { get;set;}
}

var source = new Source() { Enum = MyEnum.A };
var expectation = new Expectation() {Enum = "A"};

//With V6 this assertion will fail but in V5 it will pass
expectation.Should().BeEquivalentTo(source, options => options.ComparingEnumsByName());

How can I assert the objects above with FluentAssertions? The behaviour I want is for the assertions to be made on the ToString representation of the enum.

As I side note, I get different behaviour when I swap the expectation with source. Shouldn't they be equivalent?

CodePudding user response:

Your expectation defines the Enum property as being a string, and the options you provide are used to instruct FA how the members of the expectation should be compared to the subject. So in this case, ComparingEnumsByName doesn't do anything since the property involved is a string. What you could do instead is to use an anonymous type as the expectation.

CodePudding user response:

You can define a more relaxed equivalency step to handle string/enum comparisons.

class RelaxedEnumEquivalencyStep : IEquivalencyStep
{
    public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
    {
        if (comparands.Subject is string subject && comparands.Expectation?.GetType().IsEnum == true)
        {
            AssertionScope.Current
                .ForCondition(subject == comparands.Expectation.ToString())
                .FailWith(() =>
                {
                    decimal? subjectsUnderlyingValue = ExtractDecimal(comparands.Subject);
                    decimal? expectationsUnderlyingValue = ExtractDecimal(comparands.Expectation);

                    string subjectsName = GetDisplayNameForEnumComparison(comparands.Subject, subjectsUnderlyingValue);
                    string expectationName = GetDisplayNameForEnumComparison(comparands.Expectation, expectationsUnderlyingValue);
                    return new FailReason(
                            $"Expected {{context:string}} to be equivalent to {expectationName}{{reason}}, but found {subjectsName}.");
                });

            return EquivalencyResult.AssertionCompleted;
        }

        if (comparands.Subject?.GetType().IsEnum == true && comparands.Expectation is string expectation)
        {
            AssertionScope.Current
                .ForCondition(comparands.Subject.ToString() == expectation)
                .FailWith(() =>
                {
                    decimal? subjectsUnderlyingValue = ExtractDecimal(comparands.Subject);
                    decimal? expectationsUnderlyingValue = ExtractDecimal(comparands.Expectation);

                    string subjectsName = GetDisplayNameForEnumComparison(comparands.Subject, subjectsUnderlyingValue);
                    string expectationName = GetDisplayNameForEnumComparison(comparands.Expectation, expectationsUnderlyingValue);
                    return new FailReason(
                            $"Expected {{context:enum}} to be equivalent to {expectationName}{{reason}}, but found {subjectsName}.");
                });

            return EquivalencyResult.AssertionCompleted;
        }

        return EquivalencyResult.ContinueWithNext;
    }

    private static string GetDisplayNameForEnumComparison(object o, decimal? v)
    {
        if (o is null)
        {
            return "<null>";
        }

        if (v is null)
        {
            return '\"'   o.ToString()   '\"';
        }

        string typePart = o.GetType().Name;
        string namePart = o.ToString().Replace(", ", "|", StringComparison.Ordinal);
        string valuePart = v.Value.ToString(CultureInfo.InvariantCulture);
        return $"{typePart}.{namePart} {{{{value: {valuePart}}}}}";
    }

    private static decimal? ExtractDecimal(object o)
    {
        return o?.GetType().IsEnum == true ? Convert.ToDecimal(o, CultureInfo.InvariantCulture) : null;
    }
}

If it's just for a single test

var source = new Source() { Enum = MyEnum.A };
var expectation = new Expectation() { Enum = "A" };

expectation.Should().BeEquivalentTo(source, options => options.Using(new RelaxedEnumEquivalencyStep()));

source.Should().BeEquivalentTo(expectation, options => options.Using(new RelaxedEnumEquivalencyStep()));

Or if you want this globally you can set this using AssertionOptions.AssertEquivalencyUsing.

AssertionOptions.AssertEquivalencyUsing(e => e.Using(new RelaxedEnumEquivalencyStep()));

var source = new Source() { Enum = MyEnum.A };
var expectation = new Expectation() { Enum = "A" };

expectation.Should().BeEquivalentTo(source);
source.Should().BeEquivalentTo(expectation);

For completeness here are examples of the failure message when MyEnum and string do not match.

Expected property root.Enum to be equivalent to "B", but found MyEnum.A {value: 0}.
Expected property source.Enum to be <null>, but found MyEnum.B {value: 1}.
Expected property root.Enum to be equivalent to MyEnum.A {value: 0}, but found "B".
Expected property expectation.Enum to be equivalent to MyEnum.B {value: 1}, but found <null>.
  • Related