Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fluent Assertions: Using BeCloseTo on a collection of DateTime properties

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? :(
    }

}
like image 941
Andy Clarke Avatar asked Jul 16 '14 10:07

Andy Clarke


2 Answers

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;
});
like image 138
Matt Melton Avatar answered Nov 19 '22 13:11

Matt Melton


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);
like image 31
Dennis Doomen Avatar answered Nov 19 '22 11:11

Dennis Doomen