Test unitari: DateTime.Now


164

Ho alcuni test unitari che prevedono che l'ora corrente sia diversa da DateTime. Ora e ovviamente non voglio cambiare l'ora del computer.

Qual è la migliore strategia per raggiungere questo obiettivo?


memorizzare un valore precedente nel tempo corrente e confrontarlo con datetime.now, poiché il test di unità utilizza dati falsi, puoi facilmente farlo, vero?
Prashant Lakhlani l'

3
Fornire l'astrazione dell'attuale DateTime è utile non solo per i test e il debug ma anche nel codice di produzione - dipende dalle esigenze dell'applicazione.
Pavel Hodek,

1
Non utilizzare una classe statica per ottenere DateTime
Daniel Little,

e un altro semplice approccio che utilizza VirtualTime di seguito
Samuel,

Un'opzione che potrebbe funzionare e che è possibile aggiungere un sovraccarico è quella di creare un sovraccarico del metodo che accetta il metodo DateTime. Questo sarebbe quello che testerai, il metodo esistente chiamerebbe semplicemente il sovraccarico now.
Phil Cooper,

Risposte:


218

La migliore strategia è di avvolgere il tempo corrente in un'astrazione e iniettare quell'astrazione nel consumatore .


In alternativa , puoi anche definire un'astrazione temporale come contesto ambientale :

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

Questo ti permetterà di consumarlo in questo modo:

var now = TimeProvider.Current.UtcNow;

In un unit test, è possibile sostituire TimeProvider.Currentcon un oggetto Test Double / Mock. Esempio usando Moq:

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

Tuttavia, quando si esegue il test di unità con stato statico, ricordarsi sempre di abbattere l'apparecchiatura chiamando TimeProvider.ResetToDefault().


7
Nel caso di un contesto ambientale, anche se si abbassa, come si eseguono i test in parallelo?
Ilya Chernomordik,

2
@IlyaChernomordik Non dovresti iniettare ILogger o altre preoccupazioni trasversali , quindi iniettare un fornitore di tempo è ancora l'opzione migliore.
Mark Seemann,

5
@MikeK Come dice la prima frase della mia risposta: la migliore strategia è di avvolgere il tempo corrente in un'astrazione e iniettare quell'astrazione nel consumatore. Tutto il resto in questa risposta è la seconda migliore alternativa.
Mark Seemann,

3
Ciao. I Noob. Come si impedisce alle persone di accedere a DateTime.UtcNow con questa soluzione? Sarebbe facile perdere qualcuno usando TimeProvider. Quindi portando a bug nei test?
Obbles

1
Revisioni del codice @Obbles (o coppia di programmazione, che è una forma di revisione del codice live). Suppongo che uno potrebbe scrivere uno strumento che scansiona la base di codice DateTime.UtcNowe simili, ma le revisioni del codice sono comunque una buona idea.
Mark Seemann,

58

Queste sono tutte buone risposte, questo è quello che ho fatto in un progetto diverso:

Uso:

Ottieni l'ora della data REALE di oggi

var today = SystemTime.Now().Date;

Invece di usare DateTime.Now, devi usare SystemTime.Now()... Non è un cambiamento difficile ma questa soluzione potrebbe non essere l'ideale per tutti i progetti.

Time Travelling (Andiamo 5 anni in futuro)

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

Ottieni il nostro falso "oggi" (saranno 5 anni da "oggi")

var fakeToday = SystemTime.Now().Date;

Reimposta la data

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

1
Grazie, mi piace questo approccio funzionale. Oggi (due anni dopo) sto ancora usando una versione modificata della classe TimeProvider (controlla la risposta accettata), funziona davvero bene.
Pedro,

7
@crabCRUSHERclamCOLI: se hai avuto l'idea da ayende.com/blog/3408/dealing-with-time-in-tests , allora è una buona cosa collegarti ad esso.
Johann Gerell,

3
Questo concetto funziona egregiamente anche per deridere la generazione di Guid (pubblica statica Func <Guid> NewGuid = () => Guid.NewGuid ();
mdwhatcott il

2
Puoi anche aggiungere questo utile metodo: vuoto statico pubblico ResetDateTime (DateTime dateTimeNow) {var timespanDiff = TimeSpan.FromTicks (DateTime.Now.Ticks - dateTimeNow.Ticks); Now = () => DateTime.Now - timespanDiff; }
Pavel Hodek il

17
molto pericoloso. Ciò potrebbe influire su altri test unitari eseguiti in parallelo.
Amete Beata il

24

Moles:

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

Disclaimer - Lavoro su Moles


1
purtroppo non funziona con nunit e resharper.
odyth,

Sembra che ora Moles non sia supportato, sostituito con Fakes msdn.microsoft.com/en-us/library/… , senza dubbio MS lo interromperà troppo presto
danio,

17

Hai alcune opzioni per farlo:

  1. Utilizzare il framework beffardo e utilizzare un DateTimeService (implementare una piccola classe wrapper e inserirla nel codice di produzione). L'implementazione del wrapper accederà a DateTime e nei test sarai in grado di deridere la classe wrapper.

  2. Usa Typemock Isolator , può falsificare DateTime.Now e non richiede di modificare il codice in prova.

  3. Usa Moles , può anche falsificare DateTime.Now e non richiede modifiche nel codice di produzione.

Qualche esempio:

Classe wrapper usando 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; } }
}

Fake DateTime direttamente usando Isolator:

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

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

Disclaimer - Lavoro a Typemock


12

Aggiungi un assembly falso per il sistema (fai clic con il pulsante destro del mouse su Riferimento sistema => Aggiungi assembly falso).

E scrivi nel tuo metodo di prova:

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

se hai accesso a Vs Premium o ultimate Questo è di gran lunga il modo più semplice / veloce per farlo.
Rugdr,

7

Per quanto riguarda la risposta di @crabcrusherclamcollector, esiste un problema quando si utilizza tale approccio nelle query EF (System.NotSupportedException: il tipo di nodo di espressione LINQ "Invoke" non è supportato in LINQ to Entities). Ho modificato l'implementazione in questo modo:

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

Immagino per EF intendi Entity Framework? Che aspetto ha l'oggetto su cui stai usando SystemTime? Suppongo che tu abbia un riferimento a SystemTime sull'entità reale, invece dovresti usare un normale oggetto DateTime sulla tua entità e impostarlo usando SystemTime ovunque abbia senso nella tua applicazione
crabCRUSHERclamCOLLECTOR

1
Sì, l'ho testato durante la creazione della query Linq e EntityFramework6 e facendo riferimento a quella query UtcNow da SystemTime. C'era un'eccezione come descritto. Dopo aver modificato l'implementazione funziona bene.
marcinn,

Adoro questa soluzione! Grande!
mirind4,

7

Per testare un codice che dipende da System.DateTime, system.dlldeve essere deriso.

Ci sono due framework che conosco che fanno questo. Falsi e camici di Microsoft .

I falsi Microsoft richiedono l'ultimatum di Visual Studio 2012 e funzionano direttamente dal compton.

Smocks è un open source e molto facile da usare. Può essere scaricato tramite NuGet.

Quanto segue mostra una finta di System.DateTime:

Smock.Run(context =>
{
  context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1));

   // Outputs "2000"
   Console.WriteLine(DateTime.Now.Year);
});

5

Un SystemClockuso sicuro del thread ThreadLocal<T>funziona alla grande per me.

ThreadLocal<T> è disponibile in .Net Framework v4.0 e versioni successive.

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

Esempio di utilizzo:

[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();
}

1
+! Il thread locale dovrebbe evitare problemi con l'esecuzione di test paralleli e più test potenzialmente sovrascrivibili sull'istanza corrente Now.
Hux,

1
Ho provato a usare questo, ma ho avuto un problema tra i thread, quindi sono andato con un principio simile ma usando AsyncLocalpiuttosto cheThreadLocal
rrrr

@rrrr: puoi spiegare il problema che hai avuto?
Henk van Boeijen,

@HenkvanBoeijen Certo, ci siamo imbattuti in problemi quando è stato fissato l'orario e quindi stavamo aspettando un metodo asincrono. 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 Ho aggiunto una risposta con quello che ho finito usando il link
rrrr

3

Ho riscontrato questo stesso problema, ma ho trovato un progetto di ricerca di Microsoft che risolve questo problema.

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

Moles è un framework leggero per stub di test e deviazioni in .NET basato su delegati. Le talpe possono essere utilizzate per deviare qualsiasi metodo .NET, compresi i metodi non virtuali / statici in tipi sigillati

// 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!");
}

Il codice di esempio è stato modificato dall'originale.

Avevo fatto ciò che altri avevano suggerito e astratto il DateTime in un provider. Mi è sembrato sbagliato e ho pensato che fosse troppo solo per i test. Stasera inserirò questo nel mio progetto personale.


2
A proposito, questo funziona solo con VS2010. Ero arrabbiato quando ho scoperto che Moles è ora falso per VS2012 ed è disponibile solo per Premium e Ultimate Visual Studios. Nessun falso per VS 2012 Professional. Quindi non ho mai avuto modo di usarlo. :(
Bobby Cannon,

3

Una nota speciale sul deridere DateTime.Nowcon TypeMock ...

Il valore di DateTime.Now deve essere inserito in una variabile per essere deriso correttamente. Per esempio:

Questo non funziona:

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

Tuttavia, questo fa:

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

2

Oggetti finti.

Un finto DateTime che restituisce un Now appropriato per il test.


2

Sono sorpreso che nessuno abbia suggerito uno dei modi più ovvi per andare:

public class TimeDependentClass
{
    public void TimeDependentMethod(DateTime someTime)
    {
        if (GetCurrentTime() > someTime) DoSomething();
    }

    protected virtual DateTime GetCurrentTime()
    {
        return DateTime.Now; // or UtcNow
    }
}

Quindi puoi semplicemente ignorare questo metodo nel doppio del test.

Mi piace anche iniettare una TimeProviderlezione in alcuni casi, ma per altri è più che sufficiente. Probabilmente preferirei la TimeProviderversione se hai bisogno di riutilizzarla in diverse classi.

EDIT: Per chiunque sia interessato, questo si chiama aggiungere una "cucitura" alla tua classe, un punto in cui puoi agganciarti al suo comportamento per modificarlo (a scopo di test o altro) senza dover effettivamente cambiare il codice nella classe.


1

La buona pratica è quando 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; 
    } 
} 

Successivamente, puoi iniettare il tuo DateTime falso per i test unitari

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

Vedi esempio Esempio di DateTimeProvider


1

Un modo chiaro per farlo è iniettare VirtualTime. Ti permette di controllare il tempo. Prima installa VirtualTime

Install-Package VirtualTime

Ciò consente, ad esempio, di rendere il tempo che si sposta 5 volte più velocemente su tutte le chiamate a DateTime.Now o UtcNow

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

Per rallentare il tempo, ad es. 5 volte più lentamente

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

Per fare in modo che il tempo rimanga fermo

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

Spostarsi indietro nel tempo non è ancora stato testato

Ecco un test di esempio:

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

Puoi controllare altri test qui:

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

Cosa ti dà l'estensione DateTime.Now.ToVirtualTime è un'istanza di ITime che passi a un metodo / classe che dipende da ITime. un po 'di DateTime.Now.ToVirtualTime è impostato nel contenitore DI di tua scelta

Ecco un altro esempio iniettando in un agente di contrasto di classe

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

C'è anche una buona lettura sul codice di test che dipende da DateTime di Ayende qui ayende.com/blog/3408/dealing-with-time-in-tests
Samuel

1

Stavamo usando un oggetto SystemTime statico, ma si sono verificati problemi durante l'esecuzione di test di unità parallele. Ho tentato di utilizzare la soluzione di Henk van Boeijen ma ho riscontrato problemi tra i thread asincroni generati, alla fine ho usato AsyncLocal in un modo simile al seguente:

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

Fonte da https://gist.github.com/CraftyFella/42f459f7687b0b8b268fc311e6b4af08


1

Una vecchia domanda, ma ancora valida.

Il mio approccio è quello di creare una nuova interfaccia e classe per concludere la System.DateTime.Nowchiamata

public interface INow
{
    DateTime Execute();
}

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

Questa interfaccia può essere iniettata in qualsiasi classe che deve ottenere la data e l'ora correnti. In questo esempio, ho una classe che aggiunge un periodo di tempo alla data e ora correnti (un'unità testabile 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);
    }
}

E i test possono essere scritti per assicurarsi che funzioni correttamente. Sto usando NUnit e Moq ma qualsiasi framework di test lo farà

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

Questo modello funziona per funzioni che dipendono da fattori esterni, come System.Random.Next(), System.DateTime.Now.UtcNow, System.Guid.NewGuid()etc.

Vedere https://loadlimited.visualstudio.com/Stamina/_git/Stamina.Core per ulteriori esempi o ottenere il pacchetto nuget https://www.nuget.org/packages/Stamina.Core .


1

È possibile modificare la classe che si sta testando per utilizzare una Func<DateTime>che verrà passata attraverso i parametri del costruttore, quindi quando si crea un'istanza della classe in codice reale, è possibile passare () => DateTime.UtcNowal Func<DateTime>parametro e sul test è possibile passare il tempo desidera testare.

Per esempio:

    [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();
        }
    }

1
Mi piace questa risposta. Da quello che ho raccolto, tuttavia, questo potrebbe essere un aumento delle sopracciglia nel mondo C # dove c'è più attenzione alle classi / agli oggetti. Ad ogni modo, preferisco questo perché è davvero semplice e chiaro per me cosa sta succedendo.
pseudoramble

0

Ho avuto lo stesso problema, ma stavo pensando che non dovremmo usare le cose datetime impostate sulla stessa classe. perché potrebbe portare ad un uso improprio un giorno. quindi ho usato il provider come

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()
    {       
    }
}

Per i test, al progetto di test ha creato un aiuto che si occuperà di cose fisse,

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

sul codice

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

e sui test

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

Ma bisogna ricordare una cosa, a volte il vero DateTime e il DateTime del provider non agiscono allo stesso modo

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

Ho assunto che la deferenza sarebbe il massimo TimeSpan.FromMilliseconds (0.00002) . Ma il più delle volte è ancora meno

Trova l'esempio su MockSamples


0

Ecco la mia risposta a questa domanda. Combino il modello "Contesto ambientale" con IDisposable. Quindi puoi usare DateTimeProvider.Current nel tuo normale codice di programma e nel test sovrascrivi l'ambito con un'istruzione using.

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

Ecco come utilizzare il suddetto DateTimeProvider all'interno di 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<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;
        }
    }
}

0

Usando ITimeProvidersiamo stati costretti a prenderlo in uno speciale progetto comune condiviso che deve essere referenziato dal resto di altri progetti. Ma questo ha complicato il controllo delle dipendenze .

Abbiamo cercato ITimeProvidernel framework .NET. Abbiamo cercato il pacchetto NuGet e ne abbiamo trovato uno che non funzionaDateTimeOffset .

Quindi abbiamo trovato la nostra soluzione, che dipende solo dai tipi di libreria standard. Stiamo usando un'istanza di Func<DateTimeOffset>.

Come usare

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

Come registrarsi

autofac

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

( Per i futuri editor: aggiungi qui i tuoi casi ).

Come test unitario

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

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

    Assert.AreEqual(expected, actual);
}

0

Forse una soluzione meno professionale ma più semplice potrebbe essere quella di creare un parametro DateTime con il metodo consumer. Ad esempio invece di creare un metodo come SampleMethod, rendere SampleMethod1 con parametro. Il test di SampleMethod1 è più semplice

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

        }

    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.