What's a good way to overwrite DateTime.Now during testing?
My preference is to have classes that use time actually rely on an interface, such as
interface IClock
{
DateTime Now { get; }
}
With a concrete implementation
class SystemClock: IClock
{
DateTime Now { get { return DateTime.Now; } }
}
Then if you want, you can provide any other kind of clock you want for testing, such as
class StaticClock: IClock
{
DateTime Now { get { return new DateTime(2008, 09, 3, 9, 6, 13); } }
}
There may be some overhead in providing the clock to the class that relies on it, but that could be handled by any number of dependency injection solutions (using an Inversion of Control container, plain old constructor/setter injection, or even a Static Gateway Pattern).
Other mechanisms of delivering an object or method that provides desired times also work, but I think the key thing is to avoid resetting the system clock, as that's just going to introduce pain on other levels.
Also, using DateTime.Now
and including it in your calculations doesn't just not feel right - it robs you of the ability to test particular times, for example if you discover a bug that only happens near a midnight boundary, or on Tuesdays. Using the current time won't allow you to test those scenarios. Or at least not whenever you want.
Ayende Rahien uses a static method that is rather simple...
public static class SystemTime
{
public static Func<DateTime> Now = () => DateTime.Now;
}
Using Microsoft Fakes to create a shim is a really easy way to do this. Suppose I had the following class:
public class MyClass
{
public string WhatsTheTime()
{
return DateTime.Now.ToString();
}
}
In Visual Studio 2012 you can add a Fakes assembly to your test project by right clicking on the assembly you want to create Fakes/Shims for and selecting "Add Fakes Assembly"
Finally, Here is what the test class would look like:
using System;
using ConsoleApplication11;
using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace DateTimeTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestWhatsTheTime()
{
using(ShimsContext.Create()){
//Arrange
System.Fakes.ShimDateTime.NowGet =
() =>
{ return new DateTime(2010, 1, 1); };
var myClass = new MyClass();
//Act
var timeString = myClass.WhatsTheTime();
//Assert
Assert.AreEqual("1/1/2010 12:00:00 AM",timeString);
}
}
}
}