Prueba unitaria: DateTime.Now

Tengo algunas pruebas de unidad que esperan que la ‘hora actual’ sea diferente a DateTime.Now y no quiero cambiar la hora de la computadora, obviamente.

¿Cuál es la mejor estrategia para lograr esto?

La mejor estrategia es ajustar la hora actual en una abstracción e inyectar esa abstracción en el consumidor .


Alternativamente , también puede definir una abstracción de tiempo como un Contexto Ambiental :

public abstract class TimeProvider { private static TimeProvider current = DefaultTimeProvider.Instance; public static TimeProvider Current { get { return TimeProvider.current; } set { if (value == null) { throw new ArgumentNullException("value"); } TimeProvider.current = value; } } public abstract DateTime UtcNow { get; } public static void ResetToDefault() { TimeProvider.current = DefaultTimeProvider.Instance; } } 

Esto te permitirá consumirlo así:

 var now = TimeProvider.Current.UtcNow; 

En una prueba unitaria, puede reemplazar TimeProvider.Current con un objeto Test Double / Mock. Ejemplo de uso de Moq:

 var timeMock = new Mock(); timeMock.SetupGet(tp => tp.UtcNow).Returns(new DateTime(2010, 3, 11)); TimeProvider.Current = timeMock.Object; 

Sin embargo, cuando se prueban unidades con estado estático, recuerde siempre desmontar su dispositivo llamando a TimeProvider.ResetToDefault() .

Estas son todas buenas respuestas, esto es lo que hice en un proyecto diferente:

Uso:

Obtenga la fecha REAL de hoy

 var today = SystemTime.Now().Date; 

En lugar de usar DateTime.Now, necesita utilizar SystemTime.Now() … No es un cambio difícil, pero esta solución podría no ser ideal para todos los proyectos.

Tiempo de viaje (Vamos a ir 5 años en el futuro)

 SystemTime.SetDateTime(today.AddYears(5)); 

Get Our Fake “hoy” (será de 5 años a partir de “hoy”)

 var fakeToday = SystemTime.Now().Date; 

Restablecer la fecha

 SystemTime.ResetDateTime(); 

 ///  /// Used for getting DateTime.Now(), time is changeable for unit testing ///  public static class SystemTime { ///  Normally this is a pass-through to DateTime.Now, but it can be overridden with SetDateTime( .. ) for testing or debugging. ///  public static Func Now = () => DateTime.Now; ///  Set time to return when SystemTime.Now() is called. ///  public static void SetDateTime(DateTime dateTimeNow) { Now = () => dateTimeNow; } ///  Resets SystemTime.Now() to return DateTime.Now. ///  public static void ResetDateTime() { Now = () => DateTime.Now; } } 

Lunares:

 [Test] public void TestOfDateTime() { var firstValue = DateTime.Now; MDateTime.NowGet = () => new DateTime(2000,1,1); var secondValue = DateTime.Now; Assert(firstValue > secondValue); // would be false if 'moleing' failed } 

Descargo de responsabilidad – Yo trabajo en Moles

Usted tiene algunas opciones para hacerlo:

  1. Use el marco de simulación y use un DateTimeService (implemente una clase de envoltura pequeña e inyéctela al código de producción). La implementación de contenedor accederá a DateTime y en las pruebas podrá simular la clase contenedora.

  2. Use Typemock Isolator , puede simular DateTime.Now y no le pedirá que cambie el código bajo prueba.

  3. Use Moles , también puede simular DateTime.Now y no requerirá cambios en el código de producción.

Algunos ejemplos:

Clase de envoltorio usando Moq:

 [Test] public void TestOfDateTime() { var mock = new Mock(); mock.Setup(fake => fake.Now) .Returns(new DateTime(2000, 1, 1)); var result = new UnderTest(mock.Object).CalculateSomethingBasedOnDate(); } public class DateTimeWrapper : IDateTime { public DateTime Now { get { return DateTime.Now; } } } 

Fingir DateTime directamente con Isolator:

 [Test] public void TestOfDateTime() { Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2000, 1, 1)); var result = new UnderTest().CalculateSomethingBasedOnDate(); } 

Descargo de responsabilidad – Yo trabajo en Typemock

Agregue un ensamblaje falso para Sistema (haga clic derecho en Referencia del sistema => Agregar ensamblaje falso).

Y escriba en su método de prueba:

 using (ShimsContext.Create()) { System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10); MethodThatUsesDateTimeNow(); } 

Un SystemClock seguro para SystemClock usa ThreadLocal funciona muy bien para mí.

ThreadLocal está disponible en .Net Framework v4.0 y superior.

 ///  /// Provides access to system time while allowing it to be set to a fixed  value. ///  ///  /// This class is thread safe. ///  public static class SystemClock { private static readonly ThreadLocal> _getTime = new ThreadLocal>(() => () => DateTime.Now); ///  public static DateTime Today { get { return _getTime.Value().Date; } } ///  public static DateTime Now { get { return _getTime.Value(); } } ///  public static DateTime UtcNow { get { return _getTime.Value().ToUniversalTime(); } } ///  /// Sets a fixed (deterministic) time for the current thread to return by . ///  public static void Set(DateTime time) { if (time.Kind != DateTimeKind.Local) time = time.ToLocalTime(); _getTime.Value = () => time; } ///  /// Resets  to return the current . ///  public static void Reset() { _getTime.Value = () => DateTime.Now; } } 

Ejemplo de uso:

 [TestMethod] public void Today() { SystemClock.Set(new DateTime(2015, 4, 3)); DateTime expectedDay = new DateTime(2015, 4, 2); DateTime yesterday = SystemClock.Today.AddDays(-1D); Assert.AreEqual(expectedDay, yesterday); SystemClock.Reset(); } 

En cuanto a la respuesta de @crabcrusherclamcollector, existe un problema al usar ese enfoque en las consultas de EF (System.NotSupportedException: LINQ to Entities no admite el tipo de nodo de expresión LINQ ‘Invoke’). Modifiqué la implementación a eso:

 public static class SystemTime { private static Func UtcNowFunc = () => DateTime.UtcNow; public static void SetDateTime(DateTime dateTimeNow) { UtcNowFunc = () => dateTimeNow; } public static void ResetDateTime() { UtcNowFunc = () => DateTime.UtcNow; } public static DateTime UtcNow { get { DateTime now = UtcNowFunc.Invoke(); return now; } } } 

Para probar un código que depende de System.DateTime , el system.dll debe ser burlado.

Hay dos marcos que conozco que hacen esto. Microsoft falsifica y viste .

Las falsificaciones de Microsoft requieren ultimatum de visual studio 2012 y funciona directamente de compton.

Smocks es una fuente abierta y muy fácil de usar. Se puede descargar usando NuGet.

A continuación, se muestra una simulación de System.DateTime :

 Smock.Run(context => { context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1)); // Outputs "2000" Console.WriteLine(DateTime.Now.Year); }); 

Simulacros de objetos.

Un DateTime simulado que devuelve un Ahora que es apropiado para su prueba.

Me encontré con este mismo problema pero encontré un proyecto de investigación de Microsoft que resuelve este problema.

http://research.microsoft.com/en-us/projects/moles/

Moles es un marco ligero para pruebas y desvíos en .NET que se basa en delegates. Los moles se pueden usar para desviar cualquier método .NET, incluidos los métodos no virtuales / estáticos en tipos sellados

 // Let's detour DateTime.Now MDateTime.NowGet = () => new DateTime(2000,1, 1); if (DateTime.Now == new DateTime(2000, 1, 1); { throw new Exception("Wahoo we did it!"); } 

El código de muestra se modificó desde el original.

Hice lo que otros sugirieron y resumí el DateTime en un proveedor. Simplemente me sentí mal y sentí que era demasiado solo para probar. Voy a implementar esto en mi proyecto personal esta noche.

Me sorprende que nadie haya sugerido una de las formas más obvias de hacerlo:

 public class TimeDependentClass { public void TimeDependentMethod(DateTime someTime) { if (GetCurrentTime() > someTime) DoSomething(); } protected virtual DateTime GetCurrentTime() { return DateTime.Now; // or UtcNow } } 

Entonces puede simplemente anular este método en su prueba doble.

También me gusta inyectar una clase TimeProvider en algunos casos, pero para otros, esto es más que suficiente. Probablemente, yo preferiría la versión TimeProvider si necesita reutilizarla en varias clases.

EDITAR: Para cualquier persona interesada, esto se llama agregar una “costura” a su clase, un punto donde puede enganchar su comportamiento para modificarlo (para propósitos de prueba u otros) sin tener que cambiar el código en la clase.

Una nota especial sobre burlarse de DateTime.Now con TypeMock …

El valor de DateTime.Now debe colocarse en una variable para burlarlo correctamente. Por ejemplo:

Esto no funciona:

 if ((DateTime.Now - message.TimeOpened.Value) > new TimeSpan(1, 0, 0)) 

Sin embargo, esto hace:

 var currentDateTime = DateTime.Now; if ((currentDateTime - message.TimeOpened.Value) > new TimeSpan(1, 0, 0)) 

Una buena práctica es cuando DateTimeProvider implementa IDisposable.

 public class DateTimeProvider : IDisposable { [ThreadStatic] private static DateTime? _injectedDateTime; private DateTimeProvider() { } ///  /// Gets DateTime now. ///  ///  /// The DateTime now. ///  public static DateTime Now { get { return _injectedDateTime ?? DateTime.Now; } } ///  /// Injects the actual date time. ///  /// The actual date time. public static IDisposable InjectActualDateTime(DateTime actualDateTime) { _injectedDateTime = actualDateTime; return new DateTimeProvider(); } public void Dispose() { _injectedDateTime = null; } } 

A continuación, puede inyectar su DateTime falso para pruebas unitarias

  using (var date = DateTimeProvider.InjectActualDateTime(expectedDateTime)) { var bankAccount = new BankAccount(); bankAccount.DepositMoney(600); var lastTransaction = bankAccount.Transactions.Last(); Assert.IsTrue(expectedDateTime.Equals(bankAccount.Transactions[0].TransactionDate)); } 

Ver ejemplo Ejemplo de DateTimeProvider

Tengo el mismo problema, pero estaba pensando que no deberíamos usar las cosas de fecha y hora establecidas en la misma clase. porque podría llevar a un mal uso un día. así que he usado el proveedor como

 public class DateTimeProvider { protected static DateTime? DateTimeNow; protected static DateTime? DateTimeUtcNow; public DateTime Now { get { return DateTimeNow ?? System.DateTime.Now; } } public DateTime UtcNow { get { return DateTimeUtcNow ?? System.DateTime.UtcNow; } } public static DateTimeProvider DateTime { get { return new DateTimeProvider(); } } protected DateTimeProvider() { } } 

Para las pruebas, en el proyecto de prueba hizo un ayudante que se ocupará de cosas establecidas,

 public class MockDateTimeProvider : DateTimeProvider { public static void SetNow(DateTime now) { DateTimeNow = now; } public static void SetUtcNow(DateTime utc) { DateTimeUtcNow = utc; } public static void RestoreAsDefault() { DateTimeNow = null; DateTimeUtcNow = null; } } 

en el código

 var dateTimeNow = DateTimeProvider.DateTime.Now //not DateTime.Now var dateTimeUtcNow = DateTimeProvider.DateTime.UtcNow //not DateTime.UtcNow 

y en las pruebas

 [Test] public void Mocked_Now() { DateTime now = DateTime.Now; MockDateTimeProvider.SetNow(now); //set to mock Assert.AreEqual(now, DateTimeProvider.DateTime.Now); Assert.AreNotEqual(now, DateTimeProvider.DateTime.UtcNow); } [Test] public void Mocked_UtcNow() { DateTime utcNow = DateTime.UtcNow; MockDateTimeProvider.SetUtcNow(utcNow); //set to mock Assert.AreEqual(utcNow, DateTimeProvider.DateTime.UtcNow); Assert.AreNotEqual(utcNow, DateTimeProvider.DateTime.Now); } 

Pero hay que recordar una cosa, en algún momento el DateTime real y DateTime del proveedor no actúa igual

 [Test] public void Now() { Assert.AreEqual(DateTime.Now.Kind, DateTimeProvider.DateTime.Now.Kind); Assert.LessOrEqual(DateTime.Now, DateTimeProvider.DateTime.Now); Assert.LessOrEqual(DateTimeProvider.DateTime.Now - DateTime.Now, TimeSpan.FromMilliseconds(1)); } 

Supuse que la deferencia sería TimeSpan.FromMilliseconds máximo (0.00002) . Pero la mayoría de las veces es incluso menos

Encuentre la muestra en MockSamples

Una opción alternativa que no se menciona es inyectar la hora actual al método dependiente:

 public class DateTimeNowDependencyClass { ... public void ImplicitTimeDependencyMethod(Obj arg0) { this.TimeDependencyMethod(DateTime.Now, arg0); } internal void TimeDependencyMethod(DateTime now, Obj arg1) { ... } ... } 

La variación interna está abierta a pruebas unitarias, paralelas o no.

Esto se basa en el principio de que ImplicitTimeDependencyMethod es “demasiado simple de romper” (ver: http://junit.sourceforge.net/doc/faq/faq.htm#best_3 ) por lo que no necesita ser incluido en la cobertura de prueba de la unidad. Aunque debe ser tocado en las pruebas de integración de todos modos.

Dependiendo del propósito de la clase, puede ser deseable que ambos métodos sean públicos de todos modos.

Aquí está mi respuesta a esta pregunta. Combino el patrón ‘Contexto Ambiental’ con IDisposable. Entonces puede usar DateTimeProvider.Current en su código de progtwig normal y en la prueba anula el scope con una instrucción using.

 using System; using System.Collections.Immutable; namespace ambientcontext { public abstract class DateTimeProvider : IDisposable { private static ImmutableStack stack = ImmutableStack.Empty.Push(new DefaultDateTimeProvider()); protected DateTimeProvider() { if (this.GetType() != typeof(DefaultDateTimeProvider)) stack = stack.Push(this); } public static DateTimeProvider Current => stack.Peek(); public abstract DateTime Today { get; } public abstract DateTime Now {get; } public void Dispose() { if (this.GetType() != typeof(DefaultDateTimeProvider)) stack = stack.Pop(); } // Not visible Default Implementation private class DefaultDateTimeProvider : DateTimeProvider { public override DateTime Today => DateTime.Today; public override DateTime Now => DateTime.Now; } } } 

Aquí se explica cómo usar el DateTimeProvider anterior dentro de un Unit-Test

 using System; using Xunit; namespace ambientcontext { public class TestDateTimeProvider { [Fact] public void TestDateTime() { var actual = DateTimeProvider.Current.Today; var expected = DateTime.Today; Assert.Equal(expected, actual); using (new MyDateTimeProvider(new DateTime(2012,12,21))) { Assert.Equal(2012, DateTimeProvider.Current.Today.Year); using (new MyDateTimeProvider(new DateTime(1984,4,4))) { Assert.Equal(1984, DateTimeProvider.Current.Today.Year); } Assert.Equal(2012, DateTimeProvider.Current.Today.Year); } // Fall-Back to Default DateTimeProvider Assert.Equal(expected.Year, DateTimeProvider.Current.Today.Year); } private class MyDateTimeProvider : DateTimeProvider { private readonly DateTime dateTime; public MyDateTimeProvider(DateTime dateTime):base() { this.dateTime = dateTime; } public override DateTime Today => this.dateTime.Date; public override DateTime Now => this.dateTime; } } } 

Al usar ITimeProvider nos vimos obligados a llevarlo a un proyecto común compartido especial al que se debe hacer referencia desde el rest de otros proyectos. Pero esto complicó el control de las dependencias .

Buscamos ITimeProvider en .NET framework. Buscamos el paquete NuGet y encontramos uno que no puede funcionar con DateTimeOffset .

Entonces, creamos nuestra propia solución, que solo depende de los tipos de la biblioteca estándar. Estamos usando una instancia de Func .

Cómo utilizar

 public class ThingThatNeedsTimeProvider { private readonly Func now; private int nextId; public ThingThatNeedsTimeProvider(Func now) { this.now = now; this.nextId = 1; } public (int Id, DateTimeOffset CreatedAt) MakeIllustratingTuple() { return (nextId++, now()); } } 

Cómo registrarse

Autofac

 builder.RegisterInstance>(() => DateTimeOffset.Now); 

( Para futuros editores: agregue sus casos aquí ).

Cómo probar la unidad

 public void MakeIllustratingTuple_WhenCalled_FillsCreatedAt() { DateTimeOffset expected = CreateRandomDateTimeOffset(); DateTimeOffset StubNow() => expected; var thing = new ThingThatNeedsTimeProvider(StubNow); var (_, actual) = thing.MakeIllustratingTuple(); Assert.AreEqual(expected, actual); } 

Tal vez una solución menos profesional pero más simple podría ser un parámetro DateTime en el método de consumidor. Por ejemplo, en lugar de hacer un método como SampleMethod, make SampleMethod1 con parameter.Testing de SampleMethod1 es más fácil

 public void SampleMethod() { DateTime anotherDateTime = DateTime.Today.AddDays(-10); if ((DateTime.Now-anotherDateTime).TotalDays>10) { } } public void SampleMethod1(DateTime dateTimeNow) { DateTime anotherDateTime = DateTime.Today.AddDays(-10); if ((dateTimeNow - anotherDateTime).TotalDays > 10) { } } 

Una forma limpia de hacerlo es inyectar VirtualTime. Te permite controlar el tiempo. Primero instala VirtualTime

 Install-Package VirtualTime 

Eso permite, por ejemplo, hacer que el tiempo se mueva 5 veces más rápido en todas las llamadas a DateTime.Now o UtcNow

 var DateTime = DateTime.Now.ToVirtualTime(5); 

Para hacer que el tiempo se mueva más lento, por ejemplo, 5 veces más lento

 var DateTime = DateTime.Now.ToVirtualTime(0.5); 

Para hacer que el tiempo se pare, hazlo

 var DateTime = DateTime.Now.ToVirtualTime(0); 

Retroceder en el tiempo aún no ha sido probado

Aquí hay una prueba de muestra:

 [TestMethod] public void it_should_make_time_move_faster() { int speedOfTimePerMs = 1000; int timeToPassMs = 3000; int expectedElapsedVirtualTime = speedOfTimePerMs * timeToPassMs; DateTime whenTimeStarts = DateTime.Now; ITime time = whenTimeStarts.ToVirtualTime(speedOfTimePerMs); Thread.Sleep(timeToPassMs); DateTime expectedTime = DateTime.Now.AddMilliseconds(expectedElapsedVirtualTime - timeToPassMs); DateTime virtualTime = time.Now; Assert.IsTrue(TestHelper.AreEqualWithinMarginOfError(expectedTime, virtualTime, MarginOfErrorMs)); } 

Puedes ver más pruebas aquí:

https://github.com/VirtualTime/VirtualTime/blob/master/VirtualTimeLib.Tests/when_virtual_time_is_used.cs

Lo que DateTime.Now.ToVirtualTime le ofrece es una instancia de ITime que pasa a un método / clase que depende de ITime. algunos DateTime.Now.ToVirtualTime se configuran en el contenedor DI de su elección

Aquí hay otro ejemplo de inyección en un contrustor de clase

 public class AlarmClock { private ITime DateTime; public AlarmClock(ITime dateTime, int numberOfHours) { DateTime = dateTime; SetTime = DateTime.UtcNow.AddHours(numberOfHours); Task.Run(() => { while (!IsAlarmOn) { IsAlarmOn = (SetTime - DateTime.UtcNow).TotalMilliseconds < 0; } }); } public DateTime SetTime { get; set; } public bool IsAlarmOn { get; set; } } [TestMethod] public void it_can_be_injected_as_a_dependency() { //virtual time has to be 1000*3.75 faster to get to an hour //in 1000 ms real time var dateTime = DateTime.Now.ToVirtualTime(1000 * 3.75); var numberOfHoursBeforeAlarmSounds = 1; var alarmClock = new AlarmClock(dateTime, numberOfHoursBeforeAlarmSounds); Assert.IsFalse(alarmClock.IsAlarmOn); System.Threading.Thread.Sleep(1000); Assert.IsTrue(alarmClock.IsAlarmOn); } 

Estábamos usando un objeto SystemTime estático, pero tuvimos problemas para ejecutar pruebas de unidades paralelas. Intenté usar la solución de Henk van Boeijen, pero tuve problemas en hilos asincrónicos generados, terminé usando AsyncLocal de una manera similar a la siguiente:

 public static class Clock { private static Func _utcNow = () => DateTime.UtcNow; static AsyncLocal> _override = new AsyncLocal>(); public static DateTime UtcNow => (_override.Value ?? _utcNow)(); public static void Set(Func func) { _override.Value = func; } public static void Reset() { _override.Value = null; } } 

Procedencia de https://gist.github.com/CraftyFella/42f459f7687b0b8b268fc311e6b4af08