ShouldBeEquivalentTo failing for equivalent objects when the subject is a DateTime
Asked Answered
E

2

1

What I'm trying to do

I've just set up a test to ensure that a NodaTime LocalDateTime is mapped to a .NET DateTime, retaining the same date and time values. I'm using FluentAssertions' ShouldBeEquivalentTo method to compare the corresponding property values.

[TestClass]
public class LocalDateTimeMapping
{
    [TestMethod]
    public void RetainsComponentValues()
    {
        var nodatimeTime = new LocalDateTime();
        var dotnetTime = nodatimeTime.ToDateTimeUnspecified();

        dotnetTime.ShouldBeEquivalentTo(nodatimeTime,
            o => o
                .Including(d => d.Year)
                .Including(d => d.Month)
                .Including(d => d.Day)
                .Including(d => d.Hour)
                .Including(d => d.Minute)
                .Including(d => d.Second)
                .Including(d => d.Millisecond)
        );
    }
}

The problem

The test is failing:

Expected subject to be 01/01/1970 00:00:00, but found <1970-01-01>.

With configuration:
- Select property System.DateTime.Year
- Select property System.DateTime.Month
- Select property System.DateTime.Day
- Select property System.DateTime.Hour
- Select property System.DateTime.Minute
- Select property System.DateTime.Second
- Select property System.DateTime.Millisecond
- Match property by name (or throw)
- Invoke Action<DateTime> when info.RuntimeType.IsSameOrInherits(System.DateTime)
- Invoke Action<String> when info.RuntimeType.IsSameOrInherits(System.String)

I don't know what the last two lines mean.

What I've tried

  1. Running the test in the debugger to confirm that the values of each of these were the same.
  2. Seeing if the problem was a specific property by removing the Includes for the different properties.
  3. Basing the configuration on EquivalencyAssertionOptions<DateTime>.Empty() to ensure that no extra checks or properties were implicitly involved.
  4. Simplifying it all down to just the following.

    dotnetTime.ShouldBeEquivalentTo(nodatimeTime,
        o => EquivalencyAssertionOptions<DateTime>.Empty()
    );
    
Encasement answered 28/11, 2013 at 4:41 Comment(8)
I'm not familiar with FluentAssertions... it's a shame it doesn't show which property differed (or what else was wrong). Is there any way of getting it to provide more information?Perfidious
@JonSkeet, I think it normally does give you a specific error. It seems that this particular situation might be an exception.Encasement
ShouldBeEquivalentTo() is intended to be used for comparing complex object graphs rather than the primitive types part of the .NET framework. That specific action at the end of the exception report is meant to say that for DateTime properties it will use the built-in DateTime-specific assertions. So apparently one of the selected properties is of type DateTime.Drub
@DennisDoomen, in the code in my question, none of the selected properties were of type DateTime. Does that mean there is a bug in FluentAssertions, or am I misunderstanding what you mean?Encasement
No, but you are invoking the ShouldBeEquivalentTo() method on a DateTime rather than on a complex type. I never meant to support that.Drub
@DennisDoomen, it seems to work OK for LocalDateTime, which is also a struct. Anyway, thanks for clarifying that this is by design. I suggest this be treated as a design flaw or bug since I think it's unintuitive. It'd be interesting to see what other users of the library think.Encasement
It's not about being a struct, it's because we treat DateTime as a primitive for which FA contains dedicated assertions.Drub
But I see your point, there's currently no way to clear those default assertion rules.Drub
E
2

The following line from the error message indicated that some sort of special treatment was being given to the DateTime:

Invoke Action<DateTime> when info.RuntimeType.IsSameOrInherits(System.DateTime)

I tried swapping the two date-times I was comparing, and this resolved the problem:

nodatimeTime.ShouldBeEquivalentTo(dotnetTime,
    o => o
        .Including(d => d.Year)
        .Including(d => d.Month)
        .Including(d => d.Day)
        .Including(d => d.Hour)
        .Including(d => d.Minute)
        .Including(d => d.Second)
        .Including(d => d.Millisecond)
);

So I conclude that ShouldBeEquivalentTo should not be called on a .NET DateTime.

Encasement answered 1/12, 2013 at 22:28 Comment(1)
Maybe you can fix this with the WithAutoConversion option? Or maybe Fluent Assertions version 5.0 would fix this? fluentassertions.com/objectgraphs/#auto-conversionCimon
R
3

You can compare datetimes with fluent assertions, they have added lots of nice methods to chain it up now:

https://fluentassertions.com/datetimespans/

e.g.

theDatetime.Should().BeLessThan(10.Minutes()).Before(otherDatetime);
Resume answered 25/10, 2017 at 12:28 Comment(1)
The link is no longer available.Ellie
E
2

The following line from the error message indicated that some sort of special treatment was being given to the DateTime:

Invoke Action<DateTime> when info.RuntimeType.IsSameOrInherits(System.DateTime)

I tried swapping the two date-times I was comparing, and this resolved the problem:

nodatimeTime.ShouldBeEquivalentTo(dotnetTime,
    o => o
        .Including(d => d.Year)
        .Including(d => d.Month)
        .Including(d => d.Day)
        .Including(d => d.Hour)
        .Including(d => d.Minute)
        .Including(d => d.Second)
        .Including(d => d.Millisecond)
);

So I conclude that ShouldBeEquivalentTo should not be called on a .NET DateTime.

Encasement answered 1/12, 2013 at 22:28 Comment(1)
Maybe you can fix this with the WithAutoConversion option? Or maybe Fluent Assertions version 5.0 would fix this? fluentassertions.com/objectgraphs/#auto-conversionCimon

© 2022 - 2024 — McMap. All rights reserved.