Pruebas unitarias: DateTime.Now

164

Tengo algunas pruebas unitarias que esperan que la "hora actual" sea diferente a DateTime. Ahora y obviamente no quiero cambiar la hora de la computadora.

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

Pedro
fuente
almacene algún valor antiguo en la hora actual y compárelo con datetime. ahora, como la prueba de la Unidad utiliza datos falsos, puede hacerlo fácilmente, ¿no?
Prashant Lakhlani
3
Proporcionar la abstracción de DateTime actual es útil no solo para probar y depurar, sino también en el código de producción; depende de las necesidades de la aplicación.
Pavel Hodek
1
No use una clase estática para obtener el DateTime
Daniel Little
y otro enfoque simple usando VirtualTime a continuación
Samuel
Una opción que podría funcionar y que puede agregar una sobrecarga es crear una sobrecarga del método que acepta DateTime. Esta sería la que probaría, el método existente simplemente llamaría a la sobrecarga now.
Phil Cooper

Respuestas:

218

La mejor estrategia es envolver el tiempo 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.Currentcon un objeto Prueba doble / simulacro. Ejemplo usando Moq:

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

Sin embargo, cuando realice pruebas unitarias con estado estático, recuerde siempre derribar su dispositivo llamando TimeProvider.ResetToDefault().

Mark Seemann
fuente
77
En el caso de un contexto ambiental, incluso si derriba, ¿cómo hace que sus pruebas se ejecuten en paralelo?
Ilya Chernomordik
2
@IlyaChernomordik No debería tener que inyectarse ILogger u otras preocupaciones transversales , por lo que inyectar un proveedor de tiempo sigue siendo la mejor opción.
Mark Seemann
55
@MikeK Como dice la primera oración en mi respuesta: La mejor estrategia es envolver el tiempo actual en una abstracción e inyectar esa abstracción en el consumidor. Todo lo demás en esta respuesta es una segunda mejor alternativa.
Mark Seemann
3
Hola. Yo noob. ¿Cómo se evita que las personas accedan a DateTime.UtcNow con esta solución? Sería fácil para alguien perder el uso de TimeProvider. ¿Esto conduce a errores en las pruebas?
Obbles
1
@Obbles Code reviews (o programación de pares, que es una forma de revisión de código en vivo). Supongo que uno podría escribir una herramienta que escanee el código base DateTime.UtcNowy similares, pero las revisiones de código son una buena idea de todos modos.
Mark Seemann
58

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

Uso:

Obtenga la fecha y hora REAL de hoy

var today = SystemTime.Now().Date;

En lugar de usar DateTime.Now, debes usar SystemTime.Now() . Ahora ... No es un cambio difícil, pero esta solución puede no ser ideal para todos los proyectos.

Viaje en el tiempo (Vayamos 5 años en el futuro)

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

Obtenga nuestro falso "hoy" (serán 5 años a partir de 'hoy')

var fakeToday = SystemTime.Now().Date;

Restablecer la fecha

SystemTime.ResetDateTime();

/// <summary>
/// Used for getting DateTime.Now(), time is changeable for unit testing
/// </summary>
public static class SystemTime
{
    /// <summary> Normally this is a pass-through to DateTime.Now, but it can be overridden with SetDateTime( .. ) for testing or debugging.
    /// </summary>
    public static Func<DateTime> Now = () => DateTime.Now;

    /// <summary> Set time to return when SystemTime.Now() is called.
    /// </summary>
    public static void SetDateTime(DateTime dateTimeNow)
    {
        Now = () =>  dateTimeNow;
    }

    /// <summary> Resets SystemTime.Now() to return DateTime.Now.
    /// </summary>
    public static void ResetDateTime()
    {
        Now = () => DateTime.Now;
    }
}
cangrejo CRUSHERclamCOLLECTOR
fuente
1
Gracias, me gusta este enfoque funcional. Hoy (dos años después) todavía estoy usando una versión modificada de la clase TimeProvider (verifique la respuesta aceptada), funciona muy bien.
Pedro
77
@crabCRUSHERclamCOLI: Si tiene la idea de ayende.com/blog/3408/dealing-with-time-in-tests , entonces es bueno vincularlo.
Johann Gerell
3
Este concepto también funciona muy bien para burlarse de la generación de Guid (Public static Func <Guid> NewGuid = () => Guid.NewGuid ();
mdwhatcott
2
También puede agregar este método útil: public static void ResetDateTime (DateTime dateTimeNow) {var timespanDiff = TimeSpan.FromTicks (DateTime.Now.Ticks - dateTimeNow.Ticks); Ahora = () => DateTime.Now - timespanDiff; }
Pavel Hodek
17
muy peligroso Esto podría afectar otras pruebas unitarias que se ejecutan en paralelo.
Beato Amete
24

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: trabajo en Moles

Peli
fuente
1
desafortunadamente no funciona con nunit y resharper.
Odyth
Parece que Moles ahora no es compatible, reemplazado por Fakes msdn.microsoft.com/en-us/library/… , sin duda MS lo descontinuará demasiado pronto
danio
17

Tienes algunas opciones para hacerlo:

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

  2. Use el aislador Typemock , puede simular DateTime.Ahora y no requerirá que cambie el código bajo prueba.

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

Algunos ejemplos:

Clase de contenedor utilizando Moq:

[Test]
public void TestOfDateTime()
{
     var mock = new Mock<IDateTime>();
     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; } }
}

Falsificando DateTime directamente usando el aislador:

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

     var result = new UnderTest().CalculateSomethingBasedOnDate();
}

Descargo de responsabilidad: trabajo en Typemock

Elíseo
fuente
12

Agregue un ensamblaje falso para el sistema (haga clic con el botón derecho en Referencia del sistema => Agregar ensamblaje falso).

Y escribe en tu método de prueba:

using (ShimsContext.Create())
{
   System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
   MethodThatUsesDateTimeNow();
}
Dave
fuente
si tiene acceso a Vs Premium o ultimate Esta es, con mucho, la forma más fácil / rápida de hacerlo.
Rugdr
7

Con respecto a la respuesta @crabcrusherclamcollector, existe un problema al usar ese enfoque en las consultas EF (System.NotSupportedException: el tipo de nodo de expresión LINQ 'Invoke' no es compatible en LINQ to Entities). Modifiqué la implementación a eso:

public static class SystemTime
    {
        private static Func<DateTime> 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;
            }
        }
    }
marcinn
fuente
Supongo que por EF te refieres a Entity Framework? ¿Cómo se ve el objeto en el que está usando SystemTime? Supongo que tiene una referencia a SystemTime en la entidad real, en su lugar, debe usar un objeto DateTime normal en su entidad y configurarlo usando SystemTime donde tenga sentido en su aplicación
crabCRUSHERclamCOLLECTOR
1
Sí, lo probé al crear consultas linq y EntityFramework6 y hacer referencia en esa consulta UtcNow desde SystemTime. Hubo una excepción como se describe. Después de cambiar la implementación, funciona bien.
marcinn
¡Amo esta solución! ¡Excelente!
mirind4
7

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

Hay dos marcos que sé que hacen esto. Microsoft falsificaciones y batas .

Las falsificaciones de Microsoft requieren el ultimátum de Visual Studio 2012 y funcionan directamente desde el compton.

Smocks es de código abierto 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);
});
persianLife
fuente
5

Un SystemClockuso seguro de hilos ThreadLocal<T>funciona muy bien para mí.

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

/// <summary>
/// Provides access to system time while allowing it to be set to a fixed <see cref="DateTime"/> value.
/// </summary>
/// <remarks>
/// This class is thread safe.
/// </remarks>
public static class SystemClock
{
    private static readonly ThreadLocal<Func<DateTime>> _getTime =
        new ThreadLocal<Func<DateTime>>(() => () => DateTime.Now);

    /// <inheritdoc cref="DateTime.Today"/>
    public static DateTime Today
    {
        get { return _getTime.Value().Date; }
    }

    /// <inheritdoc cref="DateTime.Now"/>
    public static DateTime Now
    {
        get { return _getTime.Value(); }
    }

    /// <inheritdoc cref="DateTime.UtcNow"/>
    public static DateTime UtcNow
    {
        get { return _getTime.Value().ToUniversalTime(); }
    }

    /// <summary>
    /// Sets a fixed (deterministic) time for the current thread to return by <see cref="SystemClock"/>.
    /// </summary>
    public static void Set(DateTime time)
    {
        if (time.Kind != DateTimeKind.Local)
            time = time.ToLocalTime();

        _getTime.Value = () => time;
    }

    /// <summary>
    /// Resets <see cref="SystemClock"/> to return the current <see cref="DateTime.Now"/>.
    /// </summary>
    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();
}
Henk van Boeijen
fuente
1
+! El subproceso local debe evitar problemas con la ejecución de pruebas paralelas y múltiples pruebas que potencialmente anulan la Nowinstancia actual .
Hux
1
Intenté usar esto, pero tuve un problema en todos los subprocesos, por lo que he optado por un principio similar, pero en AsyncLocallugar de usarloThreadLocal
rrrr
@rrrr: ¿puedes explicar el problema que tuviste?
Henk van Boeijen
@HenkvanBoeijen Claro, nos encontramos con problemas cuando se estableció el tiempo y luego estábamos esperando un método asíncrono. Console.WriteLine(SystemClock.Now); SystemClock.Set(new DateTime(2017, 01, 01)); Console.WriteLine(SystemClock.Now); await Task.Delay(1000); Console.WriteLine(SystemClock.Now);
rrrr
@HenkvanBoeijen He agregado una respuesta con lo que terminé usando el enlace
rrrr
3

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 liviano para trozos de prueba y desvíos en .NET que se basa en delegados. Los lunares 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 fue modificado del original.

Había hecho lo que otros sugerían y resumí DateTime en un proveedor. Simplemente me sentí mal y sentí que era demasiado para probar. Voy a implementar esto en mi proyecto personal esta noche.

Bobby Cannon
fuente
2
Por cierto, esto solo funciona con VS2010. Estaba molesto cuando descubrí que Moles ahora es falso para VS2012 y solo está disponible para Premium y Ultimate Visual Studios. Sin falsificaciones para VS 2012 Professional. Así que nunca pude usar esto realmente. :(
Bobby Cannon
3

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

El valor de DateTime.Now debe colocarse en una variable para que esto se burle 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))
Dave Black
fuente
2

Simulacros de objetos.

Un DateTime simulado que devuelve un Now apropiado para su prueba.

S.Lott
fuente
2

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
    }
}

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

También me gusta inyectar una TimeProviderclase en algunos casos, pero para otros, esto es más que suficiente. Probablemente favorecería elTimeProvider embargo versión si necesita reutilizar esto en varias clases.

EDITAR: Para cualquier persona interesada, esto se llama agregar una "costura" a su clase, un punto donde puede engancharse a su comportamiento para modificarlo (con fines de prueba o de otro modo) sin tener que cambiar el código en la clase.

sara
fuente
1

Una buena práctica es cuando DateTimeProvider implementa IDisposable.

public class DateTimeProvider : IDisposable 
{ 
    [ThreadStatic] 
    private static DateTime? _injectedDateTime; 

    private DateTimeProvider() 
    { 
    } 

    /// <summary> 
    /// Gets DateTime now. 
    /// </summary> 
    /// <value> 
    /// The DateTime now. 
    /// </value> 
    public static DateTime Now 
    { 
        get 
        { 
            return _injectedDateTime ?? DateTime.Now; 
        } 
    } 

    /// <summary> 
    /// Injects the actual date time. 
    /// </summary> 
    /// <param name="actualDateTime">The actual date time.</param> 
    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

Miño
fuente
1

Una forma limpia de hacer esto 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 detenga

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

Retroceder en el tiempo aún no se ha 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 le brinda la extensión DateTime.Now.ToVirtualTime 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 inyectando en una clase contrustor

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);
}
Samuel
fuente
También hay una buena lectura sobre el código de prueba que depende de DateTime por Ayende aquí ayende.com/blog/3408/dealing-with-time-in-tests
Samuel
1

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

public static class Clock
{
    private static Func<DateTime> _utcNow = () => DateTime.UtcNow;

    static AsyncLocal<Func<DateTime>> _override = new AsyncLocal<Func<DateTime>>();

    public static DateTime UtcNow => (_override.Value ?? _utcNow)();

    public static void Set(Func<DateTime> func)
    {
        _override.Value = func;
    }

    public static void Reset()
    {
        _override.Value = null;
    }
}

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

rrrr
fuente
1

Una vieja pregunta, pero aún válida.

Mi enfoque es crear una nueva interfaz y clase para finalizar la System.DateTime.Nowllamada

public interface INow
{
    DateTime Execute();
}

public sealed class Now : INow
{
    public DateTime Execute()
    {
        return DateTime.Now
    }
}

Esta interfaz se puede inyectar en cualquier clase que necesite obtener la fecha y hora actuales. En este ejemplo, tengo una clase que agrega un intervalo de tiempo a la fecha y hora actuales (una unidad comprobable System.DateTime.Now.Add (TimeSpan))

public interface IAddTimeSpanToCurrentDateAndTime
{
    DateTime Execute(TimeSpan input);
}

public class AddTimeSpanToCurrentDateAndTime : IAddTimeSpanToCurrentDateAndTime
{
    private readonly INow _now;

    public AddTimeSpanToCurrentDateAndTime(INow now)
    {
        this._now = now;
    }

    public DateTime Execute(TimeSpan input)
    {
        var currentDateAndTime = this._now.Execute();

        return currentDateAndTime.Add(input);
    }
}

Y se pueden escribir pruebas para garantizar que funcione correctamente. Estoy usando NUnit y Moq pero cualquier marco de prueba servirá

public class Execute
{
    private Moq.Mock<INow> _nowMock;

    private AddTimeSpanToCurrentDateAndTime _systemUnderTest;

    [SetUp]
    public void Initialize()
    {
        this._nowMock = new Moq.Mock<INow>(Moq.MockBehavior.Strict);

        this._systemUnderTest = AddTimeSpanToCurrentDateAndTime(
            this._nowMock.Object);
    }

    [Test]
    public void AddTimeSpanToCurrentDateAndTimeExecute0001()
    {
        // arrange

        var input = new TimeSpan(911252);

        // arrange : mocks

        this._nowMock
            .Setup(a => a.Execute())
            .Returns(new DateTime(348756););

        // arrange : expected

        var expected = new DateTime(911252 + 348756);

        // act

        var actual = this._systemUnderTest.Execute(input).Result;

        // assert

        Assert.Equals(actual, expected);
    }
}

Este patrón funcionará para cualquiera de las funciones que dependen de factores externos, como System.Random.Next(), System.DateTime.Now.UtcNow, System.Guid.NewGuid()etc.

Consulte https://loadlimited.visualstudio.com/Stamina/_git/Stamina.Core para obtener más ejemplos u obtenga el paquete https://www.nuget.org/packages/Stamina.Core nuget.

Kevin Brydon
fuente
1

Puede cambiar la clase que está probando para usar una Func<DateTime>que se pasará a través de sus parámetros de constructor, por lo que cuando crea una instancia de la clase en código real, puede pasar () => DateTime.UtcNowaFunc<DateTime> parámetro y, en la prueba, puede pasar el tiempo que Deseo probar.

Por ejemplo:

    [TestMethod]
    public void MyTestMethod()
    {
        var instance = new MyClass(() => DateTime.MinValue);
        Assert.AreEqual(instance.MyMethod(), DateTime.MinValue);
    } 

    public void RealWorldInitialization()
    {
        new MyClass(() => DateTime.UtcNow);
    }

    class MyClass
    {
        private readonly Func<DateTime> _utcTimeNow;

        public MyClass(Func<DateTime> UtcTimeNow)
        {
            _utcTimeNow = UtcTimeNow;
        }

        public DateTime MyMethod()
        {
            return _utcTimeNow();
        }
    }
Nir
fuente
1
Me gusta esta respuesta Sin embargo, por lo que he reunido, esto podría ser sorprendente en el mundo de C #, donde hay más enfoque en las clases / objetos. De cualquier manera, prefiero esto porque es realmente simple y claro para mí lo que está sucediendo.
pseudoramble
0

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 conducir al mal uso algún 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 se hizo un ayudante que se ocupará de las 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 código

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

y en 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 necesito recordar una cosa, en algún momento el DateTime real y el DateTime del proveedor no actúan 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 aún menos

Encuentre la muestra en MockSamples

Dipon Roy
fuente
0

Aquí está mi respuesta de esta pregunta. Combino el patrón 'Contexto ambiental' con IDisposable. Por lo tanto, puede usar el DateTimeProvider.Current en su código de programa normal y en la prueba anula el alcance con una instrucción de uso.

using System;
using System.Collections.Immutable;


namespace ambientcontext {

public abstract class DateTimeProvider : IDisposable
{
    private static ImmutableStack<DateTimeProvider> stack = ImmutableStack<DateTimeProvider>.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 una prueba unitaria

using System;
using Xunit;

namespace ambientcontext
{
    public class TestDateTimeProvider
    {
        [Fact]
        public void TestDateTime()
        {
            var actual = DateTimeProvider.Current.Today;
            var expected = DateTime.Today;

            Assert.Equal<DateTime>(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<int>(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;
        }
    }
}
Stefc
fuente
0

Con el uso, ITimeProvidernos vimos obligados a llevarlo a un proyecto común compartido especial que debe ser referenciado desde el resto de otros proyectos. Pero esto complicó el control de las dependencias .

Buscamos el ITimeProvideren .NET Framework. Buscamos el paquete NuGet y encontramos uno que no funciona DateTimeOffset.

Así que se nos ocurrió nuestra propia solución, que depende solo de los tipos de la biblioteca estándar. Estamos usando una instancia deFunc<DateTimeOffset> .

Cómo utilizar

public class ThingThatNeedsTimeProvider
{
    private readonly Func<DateTimeOffset> now;
    private int nextId;

    public ThingThatNeedsTimeProvider(Func<DateTimeOffset> now)
    {
        this.now = now;
        this.nextId = 1;
    }

    public (int Id, DateTimeOffset CreatedAt) MakeIllustratingTuple()
    {
        return (nextId++, now());
    }
}

Cómo registrarse

Autofac

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

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

Como prueba unitaria

public void MakeIllustratingTuple_WhenCalled_FillsCreatedAt()
{
    DateTimeOffset expected = CreateRandomDateTimeOffset();
    DateTimeOffset StubNow() => expected;
    var thing = new ThingThatNeedsTimeProvider(StubNow);

    var (_, actual) = thing.MakeIllustratingTuple();

    Assert.AreEqual(expected, actual);
}
Mark Shevchenko
fuente
0

Quizás una solución menos profesional pero más simple podría ser hacer un parámetro DateTime en el método del consumidor. Por ejemplo, en lugar de hacer un método como SampleMethod, haga SampleMethod1 con el parámetro. La prueba 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)
        {

        }

    }
Mehmet
fuente