¿Cuál es una buena manera de sobrescribir DateTime.Now durante la prueba?

¿Cuál es una buena manera de sobrescribir DateTime.Now durante la prueba?

Mi preferencia es tener clases que usen el tiempo en realidad dependan de una interfaz, como

interface IClock
{
    DateTime Now { get; } 
}

Con una implementación concreta

class SystemClock: IClock
{
     DateTime Now { get { return DateTime.Now; } }
}

Luego, si lo desea, puede proporcionar cualquier otro tipo de reloj que desee para la prueba, como

class StaticClock: IClock
{
     DateTime Now { get { return new DateTime(2008, 09, 3, 9, 6, 13); } }
}

Puede haber algunos gastos generales al proporcionar el reloj a la clase que depende de él, pero eso podría manejarse con cualquier número de soluciones de inyección de dependencia (utilizando un contenedor de inversión de control, una simple inyección de constructor/establecedor o incluso un patrón de puerta de enlace estática). ).

También funcionan otros mecanismos de entrega de un objeto o método que proporciona los tiempos deseados, pero creo que la clave es evitar reiniciar el reloj del sistema, ya que eso solo generará dolor en otros niveles.

Además, usando DateTime.Now e incluirlo en sus cálculos no solo no se siente bien, sino que le priva de la capacidad de probar momentos particulares, por ejemplo, si descubre un error que solo ocurre cerca del límite de la medianoche o los martes. Usar la hora actual no le permitirá probar esos escenarios. O al menos no cuando quieras.


Ayende Rahien usa un método estático que es bastante simple...

public static class SystemTime
{
    public static Func<DateTime> Now = () => DateTime.Now;
}

Usar Microsoft Fakes para crear un shim es una manera realmente fácil de hacer esto. Supongamos que tuviera la siguiente clase:

public class MyClass
{
    public string WhatsTheTime()
    {
        return DateTime.Now.ToString();
    }

}

En Visual Studio 2012, puede agregar un ensamblaje Fakes a su proyecto de prueba haciendo clic con el botón derecho en el ensamblaje para el que desea crear Fakes/Shims y seleccionando "Agregar ensamblaje Fakes"

Finalmente, así es como se vería la clase de prueba:

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);

        }
    }
}
}