Unit testing code which is dependent on current DateTime

I had this method which was dependent on current date. It checks if today is Sun, Mon, Tue or Wed, then it gives 5 days of lead time for arrival of shipped items. If its Thur, Fri or Sat then it gives 6 days of lead time to account for the weekend.

private DateTime GetEstimatedArrivalDate()
{
    DateTime estimatedDate; 
    if (DateTime.Now.DayOfWeek >= DayOfWeek.Thursday)
    {
        estimatedDate = DateTime.Now.Date.AddDays(6);
    }
    else
    {
        estimatedDate = DateTime.Now.Date.AddDays(5);
    }
    return estimatedDate; 
}
How do I write a unit test for something like this which depends on todays date? Well, the easiest thing to do is to pass the current date in as a parameter to this function.
private DateTime GetEstimatedArrivalDate(DateTime currentDate)
{
    DateTime estimatedDate; 
    if (currentDate.DayOfWeek >= DayOfWeek.Thursday)
    {
        estimatedDate = currentDate.AddDays(6);
    }
    else
    {
        estimatedDate = currentDate.AddDays(5);
    }
    return estimatedDate; 
}
In real code I call it like this -
DateTime estimatedDate = GetEstimatedArrivalDate(DateTime.Now.Date);
And in my test code I can do this-
DateTime actual = GetEstimatedArrivalDate(new DateTime(2010, 2, 19));
DateTime expected = new DateTime(2010, 2, 25);;
Assert.AreEqual(expected, actual); 
There are a few problems with this approach. First, notice that this method is private. I can certainly make it public for the purpose of testing, but then I loose my nice encapsulation. Second, if I only have a few such methods this would work. But any more than a few would make passing in the current datetime tedious. Therefore, I would like a solution which doesn't change any method signatures and does minimal modification of the existing code.

So here's what I eventually did. I made an interface IClock.
interface IClock
{
    DateTime Now { get; }
}
I then created an implementation for the current time -
public class SystemClock : IClock
{
    public DateTime Now { get { return DateTime.Now; } }
}
And a fake implementation for my unit tests -
public class FakeClock : IClock
{
    private DateTime _testDate;

    // Constructor takes in the test date
    public FakeClock(DateTime testDate) { _testDate = testDate; }

    public DateTime Now { get { return _testDate; } }
}
Now I can change my method to take IClock as a parameter.
private DateTime GetEstimatedArrivalDate(IClock currentDate) { ... }
The real code will pass in an instance of SystemClock and my tests will pass in FakeClock. But frankly this is no better than my previous approach. I am still passing in a date. What I really want to do is use dependency injection here.
private DateTime GetEstimatedArrivalDate()
{
    IClock clock = StructureMap.ObjectFactory.GetInstance<IClock>(); 
    if (clock.Now.DayOfWeek >= DayOfWeek.Thursday)
    {
        estimatedDate = clock.Now.Date.AddDays(6);
    }
    else
    {
        estimatedDate = clock.Now.Date.AddDays(5);
    }
    return estimatedDate; 
}
I am using StructureMap as my DI tool. I can configure the SystemClock as the default type of IClock for my real code.
<DefaultInstance PluginType="IClock"  PluggedType="SystemClock" />
And I can inject the FakeClock in my unit test like this -
StructureMap.ObjectFactory.Inject(typeof(IClock), new FakeClock(new DateTime(2009, 2, 19)));

DateTime actual = GetEstimatedArrivalDate();
DateTime expected = new DateTime(2010, 2, 25);;
Assert.AreEqual(exprected, actual);

So this works. But I can still do one more refactoring which will make this even better. I can refactor out the instantiation of the IClock object into another class called TestableClock.
public class TestableClock
{
    public static DateTime Now
    {
        get
        {
            IClock clock = StructureMap.ObjectFactory.GetInstance<IClock>();
            return clock.Now; 
        }
    }
}
Using this my function changes to -
private DateTime GetEstimatedArrivalDate()
{
    DateTime estimatedDate; 
    if (TestableClock.Now.DayOfWeek >= DayOfWeek.Thursday)
    {
        estimatedDate = TestableClock.Now.Date.AddDays(6);
    }
    else
    {
        estimatedDate = TestableClock.Now.Date.AddDays(5);
    }
    return estimatedDate; 
}
Compare this to my original method. There is no change in the body of the method, except that DateTime.Now is now TestableClock.Now. Also notice the name I have chosen - TestableClock. This is to make my code intentions clear. I use TestableClock where ever there is business logic around current date/time. This enables the unit tests to plug custom dates to test that logic. It is not necessary to use TestableClock for all current date/times. Just where ever I want the logic to be unit tested.

To conclude, this approach has worked out very nicely for me.
  1. There is no impact on my existing code. The signature and body of the method did not change.
  2. I am still free to use DateTime.Now where ever I want. Only the places which needs unit testing around dates will use TestableClock.
  3. I like the name TestableClock. A new developer looking at this will have very little difficulty understanding the intention behind this code. Any other name like Clock.Now or SystemClock.Now will make them wonder why did I not use DateTime.Now.
  4. And I solved my original problem, which was how to unit test code which depends on current date.

No comments:

Post a Comment

I am a programmer based in Seattle, WA. This is a space where I put notes from my programming experience, reading and training. To browse the list of all articles on this site please go here. You can contact me at rohit.net@gmail.com.