Search code examples
c#unit-testingprecisionmstestfluent-assertions

FluentAssertions: Custom Double Comparer


I try to get started with FluentAssertions but quite soon I ran in a problem. I use it along with MSTest. The test below fails what shouldn´t happen (from my point of view).

[TestMethod]
        public void DoubleTest()
        {
            double temp1 = 0.1 - 0.025;
            double temp2 = 0.075;

            temp1.ShouldBeEquivalentTo(temp2, options => options.Using<double>
            (a => a.Subject.HasMinimalDifference(a.Expectation, 1)).WhenTypeIs<double>());
        }

public static bool HasMinimalDifference(double value1, double value2, int units)
   {
      long lValue1 = BitConverter.DoubleToInt64Bits(value1);
      long lValue2 = BitConverter.DoubleToInt64Bits(value2);

      // If the signs are different, return false except for +0 and -0.
      if ((lValue1 >> 63) != (lValue2 >> 63))
      {
         if (value1 == value2)
            return true;

         return false;
      }

      long diff = Math.Abs(lValue1 - lValue2);

      if (diff <= (long) units)
         return true;

      return false;
   }

HasMinimalDifference

I did quite some research but my impression is that Fluent Assertions ignores a custom double comparer. Or is it just me? I really want to use a custom double comparer as this might get extended beyound the MS-example above in the future. Any help would be appreciated.


Solution

  • Two things seems to be going on here.

    1) Using expects an action doing an assertion.

    Using<TProperty>(Action<IAssertionContext<TProperty>> action)

    You can workaround that by using HasMinimalDifference(a.Subject, a.Expectation, 1).Should().BeTrue().

    2) Since you're using ShouldBeEquivalentTo you must be running v4.19.4 or older since we removed that method in v5.

    I can reproduce the bug in v4.19.4.

    You're hitting a bug where custom assertions were not applied on the root object. The bug was fixed in https://github.com/fluentassertions/fluentassertions/pull/1033 and released as part of v5.7.0.

    [TestMethod]
    public void DoubleTest()
    {
        object temp1 = 0.1 - 0.025;
        object temp2 = 0.075;
    
        temp1.Should().BeEquivalentTo(temp2, options => options
            .Using<double>(a => HasMinimalDifference(a.Subject, a.Expectation, 1).Should().BeTrue())
            .WhenTypeIs<double>());
    }
    
    public static bool HasMinimalDifference(double value1, double value2, int units)
    {
        long lValue1 = BitConverter.DoubleToInt64Bits(value1);
        long lValue2 = BitConverter.DoubleToInt64Bits(value2);
    
        // If the signs are different, return false except for +0 and -0.
        if ((lValue1 >> 63) != (lValue2 >> 63))
        {
            if (value1 == value2)
                return true;
    
            return false;
        }
    
        long diff = Math.Abs(lValue1 - lValue2);
    
        if (diff <= (long)units)
            return true;
    
        return false;
    }
    

    I haven't looked into what HasMinimalDifference computes, but here's the idiomatic way to perform a relaxed comparison for all doubles. Docs

    [TestMethod]
    public void DoubleTest()
    {
        object temp1 = 0.1 - 0.025;
        object temp2 = 0.075;
    
        temp1.Should().BeEquivalentTo(temp2, options => options
            .Using<double>(a => a.Subject.Should().BeApproximately(a.Expectation, 1))
            .WhenTypeIs<double>());
    }