Search code examples
c#fluent-assertions

How to control "Equality" for dictionary members using FluentAssertions


Is there a way, with FluentAssertions, to control how the values of a dictionary are compared for equality ?

I have a class, one property of which is a dictionary (string/double). I would like to compare two instances of the class (expected and actual), and for the dictionary members, to specify how "equality" is determined.

Assuming I have a class as shown:

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var t1 = new Thing();
        t1.Name = "Bob";
        t1.Values.Add("Hello", 100.111);
        t1.Values.Add("There", 100.112);
        t1.Values.Add("World", 100.113);

        var t2 = new Thing();
        t2.Name = "Bob";
        t2.Values.Add("Hello", 100.111);
        t2.Values.Add("There", 100.112);
        t2.Values.Add("World", 100.1133);

        t1.Should().BeEquivalentTo(t2);
    }
}

public class Thing
{
    public string Name { get; set; }

    public Dictionary<string, double> Values { get; set; } = new Dictionary<string, double>();
}

I would like to be able to specify how, for example, the "World" entry in the dictionary is compared. In reality, it may be that the values could be very large, or the same over 10 decimal places (but not thereafter) but I think I may need to say something like "the same if less than 1% difference).

I like the way FluentAssertions tells me the member and why they are not the same, and have tried a custom IAssertionRule (using Options lambda), but that only seemed to compare the class properties, not the members of the dictionary.

I don't own the classes being compared, so cannot override the "Equal" method, and I cannot find a way to specify a custom comparer (IEquatable) - but I suspect I would lose the fluent details of why they are not the same.

If it were possible, but that any method would also apply to the doubles that were properties of the Class (as opposed to values in the dictionary), that would be ok.

Thanks.


Solution

  • Following on from Nkosi answer, this is an example of a BeApproximately that I am using (to allow using BeApproximately with a decimal?):

        [CustomAssertion]
        public static void BeApproximately(this NullableNumericAssertions<decimal> value, decimal? expected, decimal precision, string because = "",
            params object[] becauseArgs)
        {
            if (expected == null)
                value.BeNull(because);
            else
            {
                if (!Execute.Assertion.ForCondition(value.Subject != null).BecauseOf(because)
                    .FailWith($"Expected {{context:subject}} to be '{expected}' {{reason}} but found null"))
                    return;
    
                Decimal num = Math.Abs(expected.Value - (Decimal) value.Subject);
    
                Execute.Assertion.ForCondition(num <= precision).BecauseOf(because, becauseArgs).FailWith("Expected {context:value} to approximate {1} +/- {2}{reason}, but {0} differed by {3}.", (object) value.Subject, (object) expected.Value, (object) precision, (object) num);
            }
        }