Testes Unitários: DateTime.Now

Eu tenho alguns testes de unidade que espera que o ‘tempo atual’ seja diferente de DateTime.Now e eu não quero mudar o tempo do computador, obviamente.

Qual é a melhor estratégia para conseguir isso?

A melhor estratégia é envolver o tempo 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 de ambiente :

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ê consuma assim:

 var now = TimeProvider.Current.UtcNow; 

Em um teste de unidade, você pode replace TimeProvider.Current por um object Test Double / Mock. Exemplo usando Moq:

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

No entanto, quando o teste de unidade com estado estático, lembre-se sempre de derrubar seu equipamento chamando TimeProvider.ResetToDefault() .

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

Uso:

Obtenha a data de hoje REAL Time

 var today = SystemTime.Now().Date; 

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

Viagem no Tempo (Vamos 5 anos no futuro)

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

Get Our Fake “hoje” (será de 5 anos a partir de hoje)

 var fakeToday = SystemTime.Now().Date; 

Repor a data

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

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 – Eu trabalho em Moles

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

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

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

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

Alguns exemplos:

Classe de invólucro 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; } } } 

Fingindo o DateTime diretamente usando o Isolator:

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

Disclaimer – Eu trabalho na Typemock

Adicione um falso assembly para o System (clique com o botão direito do mouse em System reference => Add fake assembly).

E escreva no seu método de teste:

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

Um thread-safe SystemClock usando ThreadLocal funciona muito bem para mim.

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

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

Em relação à resposta de @crabcrusherclamcollector, há um problema ao usar essa abordagem em 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 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 testar um código que depende do System.DateTime , o system.dll deve ser escarnecido.

Existem dois frameworks que eu conheço que fazem isso. Microsoft falsificações e Smocks .

As fraudes da Microsoft exigem o ultimato visual do estúdio 2012 e funcionam diretamente do compton.

Smocks é uma fonte aberta e muito fácil de usar. Pode ser baixado usando o NuGet.

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

Objetos Simulados.

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

Eu me deparei com este mesmo problema, mas encontrei um projeto de pesquisa da Microsoft que resolve esse problema.

http://research.microsoft.com/pt-br/projects/moles/

Moles é uma estrutura leve para stubs de teste e desvios no .NET que é baseada em delegates. As Moles podem ser usadas para desviar qualquer método .NET, incluindo methods não virtuais / estáticos em tipos selados.

 // 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 exemplo foi modificado do original.

Eu tinha feito o que outro sugeriu e abstrair o DateTime em um provedor. Apenas me senti errado e senti que era demais apenas para testes. Vou implementar isso no meu projeto pessoal esta noite.

Estou surpreso que ninguém sugeriu uma das maneiras mais óbvias de ir:

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

Então você pode simplesmente replace este método no seu teste duplo.

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

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

Uma nota especial em mocking DateTime.Now com TypeMock …

O valor de DateTime.Now deve ser colocado em uma variável para que isso seja reproduzido 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)) 

Boa prática é quando 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; } } 

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

Eu tenho o mesmo problema, mas eu estava pensando que não devemos usar as coisas de datetime set na mesma class. porque poderia levar a um 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 vai lidar com as coisas set,

 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 precisa lembrar de uma coisa, em algum momento, o verdadeiro DateTime e o DateTime do provedor não funciona da mesma forma

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

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

Encontre a amostra em MockSamples

Uma opção alternativa que não é mencionada é injetar a hora atual no método dependente:

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

A variação interna é aberta para testes unitários, paralelos ou não.

Isso é baseado no princípio de que ImplicitTimeDependencyMethod é “muito simples de quebrar” (consulte: http://junit.sourceforge.net/doc/faq/faq.htm#best_3 ), portanto, não precisa ser incluído na cobertura de teste de unidade. Embora deva ser tocado em testes de integração de qualquer maneira.

Dependendo do propósito da class, pode ser desejável ter esses dois methods públicos de qualquer maneira.

Aqui está o meu anwer desta questão. Eu combino o padrão ‘Ambiente de Contexto’ com IDisposable. Assim, você pode usar o DateTimeProvider.Current em seu código de programa normal e, no teste, você substitui o escopo por uma instrução 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; } } } 

Aqui está como usar o DateTimeProvider acima dentro de um 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; } } } 

Usando o ITimeProvider , fomos forçados a levá-lo para um projeto compartilhado comum que deve ser referenciado a partir do restante de outros projetos. Mas isso complicou o controle das dependencies .

Procuramos pelo ITimeProvider no framework .NET. Procuramos pelo pacote NuGet e encontramos um que não funciona com DateTimeOffset .

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

Como usar

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

Como registrar

Autofac

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

( Para futuros editores: anexe seus casos aqui ).

Como testar a 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); } 

Talvez menos profissional, mas mais simples solução poderia ser fazer um parâmetro DateTime no método consumer.For exemplo em vez de tornar o método como SampleMethod, make SampleMethod1 com parameter.Testing 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) { } } 

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

 Install-Package VirtualTime 

Isso permite, por exemplo, fazer o tempo que se move 5 vezes mais rápido 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 ficar parado

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

Retroceder no tempo ainda não foi testado

Aqui está um teste de amostra:

 [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 fornece é uma instância do ITime que você passa para um método / class que depende do ITime. algum DateTime.Now.ToVirtualTime é configurado no contêiner DI de sua escolha

Aqui está outro exemplo injetando em um contrustor de class

 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ávamos usando um object estático SystemTime, mas tivemos problemas ao executar testes de unidade paralelos. Eu tentei usar a solução de Henk van Boeijen, mas tive problemas através de threads asynchronouss gerados, acabei usando o AsyncLocal de uma maneira semelhante a esta abaixo:

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

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