Fluent Assertions: Using BeCloseTo on a collection of DateTime properties
Asked Answered
I

3

20

I'm processing a number of items, each of which contain a DateProcessed property (a nullable DateTime) and want to Assert that the property is set to the current date. By the time it gets through the processing routine the dates are all slightly different.

I want to Test that all the DateProcessed properties have a relativity (100ms) recent DateTime.

Fluent Assertions has the .BeCloseTo method which works perfectly for a single item. But I want to use that for the entire collection. But it's not available via the Contains() when looking at a collection.

A simplified example ...

[TestFixture]
public class when_I_process_the_items
{
    [SetUp]
    public void context()
    {
        items = new List<DateTime?>(new [] { (DateTime?)DateTime.Now, DateTime.Now, DateTime.Now } );
    }

    public List<DateTime?> items;

    [Test]
    public void then_first_item_must_be_set_to_the_current_time()
    {
        items.First().Should().BeCloseTo(DateTime.Now, precision: 100);
    }

    [Test]
    public void then_all_items_must_be_set_to_the_current_time()
    {
        items.Should().Contain .... //Not sure? :(
    }

}
Iterative answered 16/7, 2014 at 10:28 Comment(0)
S
4

As a direct answer to your question, you can do something like items.Should().OnlyContain(i => (i - DateTime.Now) < TimeSpan.FromMilliseconds(100)).

However, a unit test that relies on DateTime.Now is a very bad practice. What you can do is to introduce something like this:

public static class SystemContext
{
    [ThreadStatic]
    private static Func<DateTime> now;

    public static Func<DateTime> Now
    {
        get { return now ?? (now = () => DateTime.Now); }
        set { now = value; }
    }
}

Then instead of referring to DateTime.Now, use SystemContext.Now() to get the current time. If you do that, you can 'set' the current time for a particular unit test like this:

SystemContext.Now = () => 31.March(2013).At(7, 0);
Sihon answered 16/7, 2014 at 11:13 Comment(3)
How can this answer not be useful? I'm providing a direct answer as well as a way to avoid this situation in the first place.Sihon
Despite answer removing the need for the question this answer does not answer the question posed in the titleDomiciliate
Actually, my answer directly answered the question AND offered an alternative.Sihon
D
43

You can do this in 3.5 by configuring the options to ShouldBeEquivalentTo. For example:

result.ShouldBeEquivalentTo(expected, options =>
{
    options.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation)).WhenTypeIs<DateTime>();
    return options;
});

or succintly:

result.ShouldBeEquivalentTo(expected, options => options.Using<DateTimeOffset>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation)).WhenTypeIs<DateTimeOffset>());

If you want to set this up globally, implement the following in your testing framework's fixture setup method:

AssertionOptions.AssertEquivalencyUsing(options =>
{
    options.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation)).WhenTypeIs<DateTime>();
    options.Using<DateTimeOffset>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation)).WhenTypeIs<DateTimeOffset>();
    return options;
});
Domiciliate answered 5/8, 2015 at 9:17 Comment(1)
My version is 6.11. I have to use BeCloseTo(ctx.Expectation, precision: TimeSpan.FromMilliseconds(100))Parthen
S
4

As a direct answer to your question, you can do something like items.Should().OnlyContain(i => (i - DateTime.Now) < TimeSpan.FromMilliseconds(100)).

However, a unit test that relies on DateTime.Now is a very bad practice. What you can do is to introduce something like this:

public static class SystemContext
{
    [ThreadStatic]
    private static Func<DateTime> now;

    public static Func<DateTime> Now
    {
        get { return now ?? (now = () => DateTime.Now); }
        set { now = value; }
    }
}

Then instead of referring to DateTime.Now, use SystemContext.Now() to get the current time. If you do that, you can 'set' the current time for a particular unit test like this:

SystemContext.Now = () => 31.March(2013).At(7, 0);
Sihon answered 16/7, 2014 at 11:13 Comment(3)
How can this answer not be useful? I'm providing a direct answer as well as a way to avoid this situation in the first place.Sihon
Despite answer removing the need for the question this answer does not answer the question posed in the titleDomiciliate
Actually, my answer directly answered the question AND offered an alternative.Sihon
I
-1

I've just solved it like this ...

[Test]
public void then_all_items_must_be_set_to_the_current_time()
{
    items.Should().OnlyContain(x => DateTime.Now.Subtract(x.Value).Milliseconds <= 100);
}

But if anyone knows of an 'out the box' Fluent Assertions way of doing it, please let me know.

Iterative answered 16/7, 2014 at 11:0 Comment(1)
This will ensure at least one item matches the predicate. My original answer still stands.Sihon

© 2022 - 2024 — McMap. All rights reserved.