Sunday, February 1, 2009

When time is of the essence

To write tests for classes with strong, hard coded, dependencies is always hard. One very common dependency is the dependency on the DateTime.Now-property. For example we have a “HelloWorld”-class (I’ve never written a Hello World-example actually so I just had to) that should greet us differently depending on the time of day.

   1: public class HelloWorld
   2: {
   3:     public string Greet()
   4:     {
   5:         var hour = DateTime.Now.Hour; 
   6:  
   7:         if (hour > 6 && hour < 12)
   8:         {
   9:             return "Good morning world!";
  10:         } 
  11:  
  12:         if (hour > 20 || hour <= 6)
  13:         {
  14:             return "Good night world!";
  15:         } 
  16:  
  17:         return "Hello world!";
  18:     }
  19: }

This code is very hard to test because we would have to either run the test on different times of the day to see that it returns the correct result or we would have to make the test set the system time in order to test the output. What we would like to do is to stub, mock or in some other way fake out the DateTime.Now property but we can’t since it’s a static. To break this dependency we’ll have to add some layer of indirection between the code and the static property. This is where dependency injection comes into play. As the code is right now, there’s no way for someone looking from the outside to see that this class has a dependency on the DateTime.Now-property. Using dependency injection and more specifically constructor injection makes this very clear.

First of all, let’s create our “layer of indirection”, the SystemTime-delegate which is a simple delegate type that returns the current time of the system.:

   1: public delegate DateTime SystemTime();

Now we make this dependency explicit by having our class take an instance of a SytemTime-delegate in it’s constructor and also we’ll use this delegate to access the system time rather than the DateTime.Now-property:

   1: public class HelloWorld
   2: {
   3:     SystemTime systemTime; 
   4:  
   5:     public HelloWorld(SystemTime systemTime)
   6:     {
   7:         this.systemTime = systemTime;    
   8:     } 
   9:  
  10:     public string Greet()
  11:     {
  12:         var hour = systemTime().Hour; 
  13:  
  14:         if (hour > 6 && hour < 12)
  15:         {
  16:             return "Good morning world!";
  17:         } 
  18:  
  19:         if (hour > 20 || hour <= 6)
  20:         {
  21:             return "Good night world!";
  22:         } 
  23:  
  24:         return "Hello world!";
  25:     }
  26: }
  27:  

Now it’s very easy to create tests that verify the output at different times by passing SystemTime-delegates that provide different times of the day:

   1: [TestFixture]
   2: public class HelloWorldTests
   3: {
   4:     [Test]
   5:     public void Greet_should_return_good_morning_in_the_morning()
   6:     {
   7:         HelloWorld greeter = new HelloWorld(() => DateTime.Parse("2000-01-01 07:00", CultureInfo.InvariantCulture));
   8:         
   9:         var greetingPhrase = greeter.Greet();
  10:  
  11:         Assert.AreEqual("Good morning world!", greetingPhrase);
  12:     }
  13:  
  14:     [Test]
  15:     public void Greet_should_return_good_night_in_the_evening()
  16:     {
  17:         HelloWorld greeter = new HelloWorld(() => DateTime.Parse("2000-01-01 21:00", CultureInfo.InvariantCulture));
  18:  
  19:         var greetingPhrase = greeter.Greet();
  20:  
  21:         Assert.AreEqual("Good night world!", greetingPhrase);
  22:     }
  23: }

And of course for production code you’d just pass in a delegate that access the DateTime.Now-property, preferably using an IoC-container, but it could certainly be done manually.

   1: var greeter = new HelloWorld(() => DateTime.Now);

No comments:

Post a Comment