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.
- There is no impact on my existing code. The signature and body of the method did not change.
- I am still free to use DateTime.Now where ever I want. Only the places which needs unit testing around dates will use TestableClock.
- 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.
- And I solved my original problem, which was how to unit test code which depends on current date.
No comments:
Post a Comment