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:
1 2 3 4 5 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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:
1 2 3 4 5 6 7 8 9 |
[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<int>(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.