Testing Against The Current Time
This is the third in a small series of posts about testing against non-determinism. In this installation, I'm going to cover how to deal with the current time or date.
If you have logic that is dependent of the current time or date, test results will vary according to the time you run your tests. As always, my examples tend toward the overly simplistic, but consider this method:
public bool IsTodayAWeekDay()
{
DateTime now = DateTime.Now;
if ((now.DayOfWeek == DayOfWeek.Saturday) ||
(now.DayOfWeek == DayOfWeek.Sunday))
{
return false;
}
return true;
}
The main point of this example is that the code braches on the value of DateTime.Now. Obviously, if you execute a test against this method on a weekday, the method will return true, but otherwise it will return false.
[TestMethod]
public void UseNonDeterministicTimeConsumer()
{
NonDeterministicTimeConsumer tc = new NonDeterministicTimeConsumer();
bool result = tc.IsTodayAWeekDay();
// How to verify result?
}
As you can see, you can't really test this method. A quick fix for this could be to change the method's signature to accept the current time as a parameter, but this may not be a good idea for a number of reasons (which I'll leave as an exercise for the interested reader).
If changing the method's signature is not an option, then how can you test such a method?
This is a case where the Provider Injection pattern really comes into its own. Unlike other Inversion of Control patterns, Provider Injection can delay the creation of a dependency until it is needed. Since the current time is continually changing, you should never request an instance before you need it, and that's exactly what you can do with Provider Injection.
As always, I'm using Service Locator 2's ServiceProvider<T>, but you can always use an implementation of IServiceProvider and do a bit of casting if you want to stick to the BCL proper.
public partial class TimeConsumer
{
private ServiceProvider<DateTime> dateTimeProvider_;
public TimeConsumer(ServiceProvider<DateTime> dateTimeProvider)
{
this.dateTimeProvider_ = dateTimeProvider;
}
public bool IsTodayAWeekDay()
{
DateTime now = this.dateTimeProvider_.Create("Now");
if ((now.DayOfWeek == DayOfWeek.Saturday) ||
(now.DayOfWeek == DayOfWeek.Sunday))
{
return false;
}
return true;
}
}
The trick is to defer creation of the DateTime instance until you need it. Incidentally, you see here a rather nice feature of Service Locator 2 illustrated: Requesting a DateTime instance named "Now" allows Service Locator to distinguish between several requested DateTime objects (you might also want to be able to create what corresponds to DateTime.Today), but it also allows Service Locator to infer the correct creation strategy for the requested instance. This means that unless you intercept it by presetting or configuring a value, ServiceProvider<DateTime> will simply use DateTime.Now.
In the test, however, you do want to intercept the default strategy by presetting the current time to a predefined value:
[TestMethod]
public void CheckMonday()
{
ServiceProvider<DateTime> dateTimeProvider =
new ServiceProvider<DateTime>();
// 2007-05-07 is a Monday
dateTimeProvider.Preset(new DateTime(2007, 5, 7), "Now");
TimeConsumer tc = new TimeConsumer(dateTimeProvider);
Assert.IsTrue(tc.IsTodayAWeekDay());
}
When executed, the Create method will always return the preset instance - in this case alway May 7th, 2007, which is a Monday. Obviously, you can write similar tests for the other days of the week.
Next: Testing Against The Passage of Time
Comments
Anonymous
May 12, 2007
PingBack from http://blogs.msdn.com/ploeh/archive/2007/05/11/TestingAgainstNonDeterminism.aspxAnonymous
May 12, 2007
This is the second in a small series of posts about testing against non-determinism. In this installation,Anonymous
May 13, 2007
If you do not take the time zone into consideration, CheckMonday() may not return the same result if run on two different computers. While UTC is good for log files, I would recommend a time zone that takes account of daylight saving times; otherwise you will have to change any reconfiguration twice a year. KostAnonymous
May 13, 2007
Hi Kost :) Thank you for your comment. I'm not sure I understand why the CheckMonday test may yield a different result on different machines. No matter the time zone of the machine, the injected date is May 7th, 2007. Since the test is calling the test target via an in-process call, the time zone of the test code is always the same as the time zone of the test target. Thus, I think May 7th, 2007 will always be May 7th, 2007, or am I missing your point?Anonymous
May 14, 2007
This is the fourth in a small series of posts about testing against non-determinism. In this installation,Anonymous
January 18, 2010
And what's the problem with using parameter injection? I think method should look something like this: public bool IsWeekDay(DateTime date) { if ((date.DayOfWeek == DayOfWeek.Saturday) || (date.DayOfWeek == DayOfWeek.Sunday)) { return false; } return true; } By the way, it seems to be feature of DateTime class and with C# 3.0 this can be moved to extension method DateTime.IsWeekDay().