Unit Testing DateTime.Now Without Isolation
My friend Paul pointed something out to me the other day regarding my post about TDD even when you have to discard tests. I believe that this trick was taken from the book Working Effectively With Legacy Code by Michael Feathers (though I haven’t yet read this one, so I can’t be positive.
I was writing some TDD test surrounding the following production method:
public virtual void SeedWithYearsSince(DropDownList list, int year)
{
for (int index = year; index <= DateTime.Now.Year; index++)
list.Items.Add(new ListItem(index.ToString()));
}
and the problem I was having is that any tests that I write and check in will be good through the end of 2012 and essentially have an expiration date of Jan 1st, 2013.
What Paul pointed out is that I could refactor this to the following:
protected virtual int CurrentYear
{
get
{
return DateTime.Now.Year;
}
}
public virtual void SeedWithYearsSince(DropDownList list, int year)
{
for (int index = year; index <= CurrentYear; index++)
list.Items.Add(new ListItem(index.ToString()));
}
And, once I've done that, I can introduce the following class into my test class:
public class CalenderDropDownFillerExtension : CalendarDropdownFiller
{
private int _currentYear;
protected override int CurrentYear
{
get
{
return _currentYear;
}
}
public CalenderDropDownFillerExtension(DateTimeFormatInfo formatInfo, int yearToUse) : base(formatInfo)
{
_currentYear = yearToUse;
}
}
With all that in place, I can write a test that no longer expires:
[TestMethod, Owner("ebd"), TestCategory("Proven"), TestCategory("Unit")]
public void Adds_Two_Items_When_Passed_2011()
{
var filler = new CalenderDropDownFillerExtension(new DateTimeFormatInfo(), 2012);
var list = new DropDownList();
filler.SeedWithYearsSince(list, 2011);
Assert.AreEqual(2, list.Items.Count);
}
In this test, I use the new class that requires me to specify the current year. It overrides the base class, which uses DateTime.Now in favor of the "current" year I've passed it, which has nothing to do with the non-deterministic quantity "Now". As a result, I can TDD 'til the cows come home and check everything in so that nobody accuses me of having a Canadian girlfriend. In other words, I get to have my cake and eat it too.
The technique we’ve used for our project wsa the idea taken from Ayende’s blog (). We created a new class entitled SystemTime. It has one static public Func which returns a DateTimeOffset. By default, it is set to () => DateTimeOffset.Now. In our unit tests, we set the Func to return whatever value we would like to. The downside of this approach–especially if you are using an existing code base–is that you have to replace all your calls to DateTime(Offset).Now with this SystemTime.Now() call. However, if you are approaching a new project (or an manageably small existing codebase), this approach… Read more »
Forgot to go back & fill in the URL for Ayende’s blog entry. Here it is:
http://ayende.com/blog/3408/dealing-with-time-in-tests
I read through Ayende’s post, and that does seem like a cool approach. It has the interesting side effect too of being able to “way back machine” your application to an arbitrary run date without having to mess with the system clock or run in a VM. I’ll have to play with that approach and see how I like it.
I’ve been using successfully the approach described by Mark Seemann in his book on Dependency Injection (a recommended read, by the way). It is described in his chapter on the DI pattern “Ambient Context”, that you can read for free here : https://www.manning.com/books/dependency-injection-in-dot-net (it’s in chapter 4, and it is described around page 124). Basically, the approach is to never ever use DateTime.Now or similar directly, but instead expose those via a wrapper …. but you don’t want every single class you use to have it as constructor parameters because the default implementation relying on .NET DateTime is a decent… Read more »