How-to isolate DateTime.Now in your unit tests

Updated: The code for the OverrideDateTimeWithQueue method and the test that consumed it has been updated.

I’m currently working on an application that makes some calculations depending on dates. In one scenario I wanted to ensure that a correct timeintervall is created from DateTime.Now. Since I’m not using TypeMock an easy way is to don’t access DateTime.Now directly in my code, but via instead via a static proxy, to which I can inject code that tells it what dates to return. This is not a new solution, search with Google for eg. “unit test datetime.now” and see for your self.

I also wanted the ability to Queue dates, for the ability to setup a range of expected dates that would be dequeued at each requested for DateTime.Now. One simple implementation I could think of was to let my static proxy implementation use a Func that per default just returns DateTime.Now; but of course can be overridden by the user.

Before we look at the tests, lets give some insight to the domain: With the use of a recorder I can create recordings. Each recording will get a start- and stoptime and will be created when recorder.Stop is invoked. The Recording can then be fetched/accessed via recorder.LastRecording or via an event: RecordingCompleted; raised by the recorder. The Recorder holds StartedAt and StoppedAt timestamps which are used to populate the Recording.

When I want the value of DateTime.Now in my domain, I fetch it from my proxy: DateTimeNow.Value

The Old test

[TestMethod]
public void Start_WhenSecondCall_TimeStampsAreUpdated()
{
	var recorder = RecorderFactoryForTests.Default();
	recorder.Start();
	recorder.Stop();

	DateTimeAsserts.GainsValueBetweenNow(
		() => recorder.StartedAt, recorder.Start);
	Assert.IsNull(recorder.StoppedAt);
}

DateTimeAsserts.GainsValueBetweenNow, ensures that when recorder.Start is invoked a before and after timestamp is stored away and then is the recorder.StartedAt compared against these dates. Not so good, since I can’t isolate the value.

The New test

[TestMethod]
public void Start_WhenSecondCall_TimeStampsAreUpdated()
{
    var dateTimes = new[] { 
            DateTime.Parse("2010-01-01 00:00:00"),
            DateTime.Parse("2010-01-01 00:01:00"),
            DateTime.Parse("2010-01-01 00:02:00") };
    var recorder = RecorderFactoryForTests.Default();

    TestHelper.OverrideDateTimeWithQueue(
        () =>
        {
            recorder.Start();
            recorder.Stop();
            recorder.Start();
        }, dateTimes);

    Assert.AreEqual(dateTimes[2], recorder.StartedAt);
    Assert.IsNull(recorder.StoppedAt);
}

Now, the first item in dateTimes will be consumed by recorder.Start() and assigned to recorder.StartedAt. The second element will be assigned to StoppedAt in recorder.Stop() and the last value will be assigned to recorder.StartedAt when the last recorder.Start() call is executed.

The test will of course break if let’s say the first call to recorder.Start() invokes DateTimeNow.Value two times. Another way to write the test is:

[TestMethod]
public void Start_WhenSecondCall_TimeStampsAreUpdated()
{
    var expectedStartedAt = DateTime.Parse("2010-01-01 00:02:00");
    var recorder = RecorderFactoryForTests.Default();

    recorder.Start();
    recorder.Stop();

    DateTimeNow.Set(() => expectedStartedAt);
    recorder.Start();

    Assert.AreEqual(expectedStartedAt, recorder.StartedAt);
    Assert.IsNull(recorder.StoppedAt);
}

Code for DateTimeNow

public static class DateTimeNow
{
    private static readonly object _lock;
    private static Func<DateTime> DateTimeNowFunc { get; set; }

    static DateTimeNow()
    {
        _lock = new object();
        Reset();
    }

    public static DateTime Value
    {
        get { return DateTimeNowFunc.Invoke(); }
    }

    public static void Set(Func<DateTime> dateTimeNowFunc)
    {
        lock (_lock)
        {
            DateTimeNowFunc = dateTimeNowFunc;
        }
    }

    public static void Reset()
    {
        Set(() => DateTime.Now);
    }
}

Code for the TestHelper.OverrideDateTimeWithQueue

public static void OverrideDateTimeWithQueue(Action action, params DateTime[] expectedDates)
{
    var queuedDates = new Queue<DateTime>(expectedDates);

    try
    {
        DateTimeNow.Set(queuedDates.Dequeue);
        action.Invoke();
    }
    finally
    {
        DateTimeNow.Reset();
    }
}

That’s it.

//Daniel

One thought on “How-to isolate DateTime.Now in your unit tests

  1. Pingback: DotNetShoutout

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s