Partager via


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.aspx

  • Anonymous
    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. Kost

  • Anonymous
    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().