Teste de unidade: DateTime.Now

164

Eu tenho alguns testes de unidade que esperam que o 'horário atual' seja diferente do DateTime. Agora, e não quero alterar o horário do computador, obviamente.

Qual é a melhor estratégia para conseguir isso?

Pedro
fonte
armazene algum valor antigo no tempo atual e compare-o com datetime.now, como o teste de unidade usa dados falsos, você pode fazer isso facilmente, não é?
Prashant Lakhlani
3
Fornecer abstração do DateTime atual é útil não apenas para teste e depuração, mas também no código de produção - depende das necessidades do aplicativo.
Pavel Hodek
1
Não use uma classe estática para obter o DateTime
Daniel Little
e uma outra abordagem simples usando VirtualTime abaixo
Samuel
Uma opção que pode funcionar e você pode adicionar uma sobrecarga é criar uma sobrecarga do método que aceita o DateTime. Você testaria esse método; o método existente simplesmente chamaria a sobrecarga now.
Phil Cooper

Respostas:

218

A melhor estratégia é agrupar o horário atual em uma abstração e injetar essa abstração no consumidor .


Como alternativa , você também pode definir uma abstração de tempo como um 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;
   }            
}

Isso permitirá que você o consuma assim:

var now = TimeProvider.Current.UtcNow;

Em um teste de unidade, você pode substituir TimeProvider.Currentpor um objeto Testar Duplo / Simulado. Exemplo usando Moq:

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

No entanto, ao testar a unidade com estado estático, lembre-se sempre de desmontar o equipamento ligando TimeProvider.ResetToDefault().

Mark Seemann
fonte
7
No caso de um contexto ambiental, mesmo que você desmonte, como você executa seus testes em paralelo?
Ilya Chernomordik
2
@IlyaChernomordik Você não precisa injetar o ILogger ou outras preocupações transversais ; portanto, a injeção de um provedor de tempo ainda é a melhor opção.
22414 Mark Markmann
5
@ MikeK Como a primeira frase da minha resposta diz: A melhor estratégia é agrupar o tempo atual em uma abstração e injetar essa abstração no consumidor. Tudo o resto nesta resposta é uma segunda melhor alternativa.
Mark Seemann 25/05
3
Oi. Eu Noob. Como impedir que as pessoas acessem o DateTime.UtcNow com esta solução? Seria fácil alguém perder o uso do TimeProvider. Assim, levando a erros nos testes?
Obbles
1
Revisões do código @Obbles (ou programação em pares, que é uma forma de revisão do código ativo). Suponho que alguém possa escrever uma ferramenta que varre a base de código DateTime.UtcNowe similares, mas as revisões de código são uma boa ideia.
Mark Seemann
58

Estas são todas boas respostas, foi o que fiz em um projeto diferente:

Uso:

Obter a data REAL de hoje Hora

var today = SystemTime.Now().Date;

Em vez de usar DateTime.Now, você precisa usar SystemTime.Now() ... Não é uma mudança difícil, mas esta solução pode não ser ideal para todos os projetos.

Viagem no tempo (vamos passar 5 anos no futuro)

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

Get Our Fake "today" (daqui a 5 anos a partir de 'today')

var fakeToday = SystemTime.Now().Date;

Redefinir a 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;
    }
}
crabCRUSHERclamCOLLECTOR
fonte
1
Obrigado, eu gosto dessa abordagem funcional. Hoje (dois anos depois) ainda estou usando uma versão modificada da classe TimeProvider (verifique a resposta aceita), funciona muito bem.
Pedro
7
@crabCRUSHERclamCOLI: Se você teve a ideia em ayende.com/blog/3408/dealing-with-time-in-tests , é uma coisa boa vincular-se a ela.
Johann Gerell
3
Esse conceito também funciona muito bem para zombar da geração do Guid (Func público estático <Guid> NewGuid = () => Guid.NewGuid ()
;;
2
Você também pode adicionar este método útil: public static void ResetDateTime (DateTime dateTimeNow) {var timespanDiff = TimeSpan.FromTicks (DateTime.Now.Ticks - dateTimeNow.Ticks); Agora = () => DateTime.Now - timespanDiff; }
Pavel Hodek
17
muito perigoso. Isso pode afetar outros testes de unidade em paralelo.
Amete Blessed
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
}

Isenção de responsabilidade - trabalho na Moles

Peli
fonte
1
infelizmente não funciona com nunit e re-sharper.
odyth
Looks como Moles é agora suportado, substituído com Fakes msdn.microsoft.com/en-us/library/... , não MS dúvida irá descontinuar que muito em breve
Danio
17

Você tem algumas opções para fazer isso:

  1. Use a estrutura de simulação e use um DateTimeService (implemente uma classe de wrapper pequena e injete-a no código de produção). A implementação do wrapper acessará o DateTime e nos testes você poderá zombar da classe do wrapper.

  2. Use o Typemock Isolator , ele pode falsificar o DateTime.Now e não exigirá que você altere o código em teste.

  3. Use Moles , também pode falsificar DateTime. e não exigirá alterações no código de produção.

Alguns exemplos:

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

Falsificando DateTime diretamente usando o Isolator:

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

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

Isenção de responsabilidade - trabalho na Typemock

Eliseu
fonte
12

Adicione uma montagem falsa para o sistema (clique com o botão direito do mouse em Referência do sistema => Adicionar montagem falsa).

E escreva no seu método de teste:

using (ShimsContext.Create())
{
   System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
   MethodThatUsesDateTimeNow();
}
Dave
fonte
se você tiver acesso ao Vs Premium ou Ultimate Essa é de longe a maneira mais fácil / rápida de fazer isso.
Rugdr
7

Em relação à resposta @crabcrusherclamcollector, há um problema ao usar essa abordagem nas consultas EF (System.NotSupportedException: o tipo de nó de expressão LINQ 'Invoke' não é suportado no LINQ to Entities). Eu modifiquei a implementação para isso:

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
fonte
Acho que por EF você quer dizer Entity Framework? Como é o objeto no qual você está usando o SystemTime? Meu palpite é que você tem uma referência a SYSTEMTIME na entidade real, em vez disso você deve usar um objeto DateTime regular sobre a sua entidade e configurá-lo usando SYSTEMTIME onde quer que faz sentido na sua aplicação
crabCRUSHERclamCOLLECTOR
1
Sim, eu testei ao criar consulta linq e EntityFramework6 e fazer referência nessa consulta UtcNow a partir do SystemTime. Houve exceção, conforme descrito. Depois de alterar a implementação, funciona bem.
marcinn
Eu amo essa solução! Ótimo!
mirind4 28/02
7

Para testar um código que depende do System.DateTime, osystem.dll deve ser ridicularizado.

Existem duas estruturas que eu conheço que fazem isso. Falsificações e blusas da Microsoft .

As falsificações da Microsoft exigem um ultimato do visual studio 2012 e funcionam diretamente do compton.

O Smocks é um código aberto e muito fácil de usar. Pode ser baixado usando o NuGet.

O seguinte mostra uma simulação de System.DateTime:

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

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

Um SystemClockuso seguro de threads ThreadLocal<T>funciona muito bem para mim.

ThreadLocal<T> está disponível no .Net Framework v4.0 e 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;
    }
}

Exemplo 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
fonte
1
+! O encadeamento local deve evitar problemas com a execução de testes paralelos e vários testes potencialmente substituindo a Nowinstância atual .
Hux 23/02
1
Tentou usar isso, mas tive um problema entre segmentos assim ter ido com um principal semelhante, mas utilizando AsyncLocal, em vez deThreadLocal
rrrr
@rrrr: você pode explicar o problema que teve?
Henk van Boeijen
@HenkvanBoeijen Claro, tivemos problemas quando o tempo foi definido e estávamos aguardando um método assí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 Adicionei uma resposta com o que acabei usando o link
rrrr
3

Encontrei esse mesmo problema, mas encontrei um projeto de pesquisa da Microsoft que resolve esse problema.

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

Moles é uma estrutura leve para stubs de teste e desvios no .NET que se baseia em delegados. Moles podem ser usados ​​para desviar qualquer método .NET, incluindo métodos não virtuais / estáticos em tipos fechados

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

O código de amostra foi modificado a partir do original.

Eu tinha feito o que outros sugeriam e abstraído o DateTime em um provedor. Pareceu errado e eu senti que era demais apenas para testar. Vou implementar isso no meu projeto pessoal esta noite.

Bobby Cannon
fonte
2
BTW, isso funciona apenas com o VS2010. Fiquei chateado quando descobri que o Moles agora é Fakes para o VS2012 e só está disponível para Premium e Ultimate Visual Studios. Não há falsificações para o VS 2012 Professional. Então, eu nunca consegui realmente usar isso. :(
Bobby Cannon
3

Uma nota especial sobre zombaria DateTime.Nowcom o TypeMock ...

O valor de DateTime.Now deve ser colocado em uma variável para que isso seja zombado corretamente. Por exemplo:

Isso não funciona:

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

No entanto, isso faz:

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

Zombar de objetos.

Um DateTime simulado que retorna um Agora apropriado para o seu teste.

S.Lott
fonte
2

Estou surpreso que ninguém tenha sugerido uma das maneiras mais óbvias a seguir:

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

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

Em seguida, você pode simplesmente substituir esse método em seu teste duas vezes.

Eu também gosto de injetar uma TimeProviderclasse em alguns casos, mas para outros, isso é mais do que suficiente. Eu provavelmente prefeririaTimeProvider versão se você precisar reutilizá-la em várias classes.

EDIT: Para qualquer pessoa interessada, isso é chamado de adição de "costura" à sua classe, um ponto em que você pode se conectar ao seu comportamento para modificá-lo (para fins de teste ou não) sem realmente precisar alterar o código na classe.

sara
fonte
1

A boa prática é 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; 
    } 
} 

Em seguida, você pode injetar seu DateTime falso para testes de unidade

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

Veja o exemplo Exemplo de DateTimeProvider

Mino
fonte
1

Uma maneira limpa de fazer isso é injetar o VirtualTime. Permite controlar o tempo. Primeiro instale o VirtualTime

Install-Package VirtualTime

Isso permite, por exemplo, acelerar o tempo 5 vezes mais em todas as chamadas para DateTime.Now ou UtcNow

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

Para tornar o tempo mais lento, por exemplo, 5 vezes mais lento,

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

Para fazer o tempo parar, faça

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

Retroceder no tempo ainda não foi testado

Aqui está um exemplo de teste:

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

Você pode conferir mais testes aqui:

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

O que a extensão DateTime.Now.ToVirtualTime oferece é uma instância do ITime que você passa para um método / classe que depende do ITime. some DateTime.Now.ToVirtualTime está configurado no contêiner DI de sua escolha

Aqui está outro exemplo de injeção em um controlador de 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);
}
Samuel
fonte
Há também uma boa leitura sobre o teste de código que depende DateTime por Ayende aqui ayende.com/blog/3408/dealing-with-time-in-tests
Samuel
1

Estávamos usando um objeto estático SystemTime, mas tivemos problemas ao executar testes de unidade paralelos. Tentei usar a solução de Henk van Boeijen, mas tive problemas em threads assíncronos gerados, e acabei usando o AsyncLocal de uma maneira semelhante a esta abaixo:

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: https://gist.github.com/CraftyFella/42f459f7687b0b8b268fc311e6b4af08

rrrr
fonte
1

Uma pergunta antiga, mas ainda válida.

Minha abordagem é criar uma nova interface e classe para encerrar a System.DateTime.Nowchamada

public interface INow
{
    DateTime Execute();
}

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

Essa interface pode ser injetada em qualquer classe que precise obter a data e hora atuais. Neste exemplo, tenho uma classe que adiciona um intervalo de tempo à data e hora atuais (uma unidade testável 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 testes podem ser escritos para garantir que funcionem corretamente. Estou usando NUnit e Moq, mas qualquer estrutura de teste 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);
    }
}

Esse padrão vai funcionar para todas as funções que dependem de fatores externos, como System.Random.Next(), System.DateTime.Now.UtcNow, System.Guid.NewGuid()etc.

Consulte https://loadlimited.visualstudio.com/Stamina/_git/Stamina.Core para obter mais exemplos ou obtenha o https://www.nuget.org/packages/Stamina.Core nuget package.

Kevin Brydon
fonte
1

Você pode alterar a classe que está testando para usar uma Func<DateTime>que passará pelos parâmetros do construtor; portanto, quando você cria uma instância da classe em código real, pode passar () => DateTime.UtcNowpara oFunc<DateTime> parâmetro e, no teste, pode passar o tempo em que deseja testar.

Por exemplo:

    [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
fonte
1
Eu gosto desta resposta. Pelo que eu deduzi, isso pode ser uma surpresa no mundo C #, onde há um foco maior em classes / objetos. De qualquer forma, prefiro isso porque é realmente simples e claro para mim o que está acontecendo.
pseudoramble
0

Eu tive o mesmo problema, mas estava pensando que não deveríamos usar as coisas definidas de data e hora na mesma classe. porque poderia levar ao mau uso um dia. então eu usei o provedor 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 testes, no projeto de teste fez um ajudante que lidará com coisas definidas,

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

no código

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

e em testes

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

Mas é preciso lembrar de uma coisa: em algum momento o DateTime real e o DateTime do provedor não funcionam da mesma maneira

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

Presumi que a deferência seria máxima TimeSpan.FromMilliseconds (0.00002) . Mas na maioria das vezes é ainda menos

Encontre a amostra em MockSamples

Dipon Roy
fonte
0

Aqui está a minha resposta para esta pergunta. Combino o padrão 'Contexto ambiente' com IDisposable. Portanto, você pode usar o DateTimeProvider.Current no código normal do programa e, no teste, substitui o escopo por uma instrução 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; 
    }
}
}

Aqui está como usar o DateTimeProvider acima em um teste de unidade

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
fonte
0

Usando ITimeProviderisso, fomos forçados a levá-lo para um projeto comum compartilhado especial que deve ser referenciado pelo restante de outros projetos. Mas isso complicou o controle de dependências .

Procuramos ITimeProviderno framework .NET. Pesquisamos o pacote NuGet e encontramos um que não funciona DateTimeOffset.

Por isso, criamos nossa própria solução, que depende apenas dos tipos da biblioteca padrão. Estamos usando uma instância de Func<DateTimeOffset>.

Como usar

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

Como registrar

Autofac

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

( Para futuros editores: anexe seus casos aqui ).

Como teste de unidade

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
fonte
0

Talvez uma solução menos profissional, mas mais simples, possa ser tornar um parâmetro DateTime no método do consumidor. Por exemplo, em vez de fazer um método como SampleMethod, faça SampleMethod1 com o parâmetro. O teste de SampleMethod1 é mais 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
fonte