In .NET (and some other languages) DateTime.Now
, DateTime.UtcNow
and DateTime.Today
(and their language specif equivalents) are not stub-able. Therefor, usage should be discouraged. Detecting the usage is not the challenge obviously, writing down what to do instead in such a way that it helps developers might be.
I already gave it a try for (.NET)[Rule for using a testable (date) time provider by Corniel · Pull Request #4207 · SonarSource/sonar-dotnet · GitHub]:
Adaptable static reference
A static reference that can be adjusted, ideally supports an IDisposable
method, that not only adjusts the time behaviour for the current thread only, but also for scope of the using
.
public static class Clock
{
public static DateTime UtcNow();
public static IDisposable SetTimeForCurrentThread(Func<DateTime> time);
}
Interface
An interface that relies on the (UTC) time provided:
public interface IClock
{
DateTime UtcNow();
}
Compliant code with static reference
public class Cookie
{
public DateTime? ExpirationDate { get; set; }
public bool HasExpired() => ExpirationDate .HasValue && ExpirationDate .Value < Clock.UtcNow();
}
public class CookieTest
{
[Test]
public void Cookie_has_expired_when_expiration_date_in_past()
{
using (Clock.SetTimeForCurrentThread(() => new DateTime(2017, 06, 11)))
{
Assert.IsTrue(new Cookie { ExpirationDate = new DateTime(2017, 06, 10) }.HasExpired());
}
}
}
Compliant code with interface
public class Cookie
{
public DateTime? ExpirationDate { get; set; }
public bool HasExpired(IClock clock) => ExpirationDate .HasValue && ExpirationDate .Value < clock.UtcNow();
}
public class CookieTest
{
[Test]
public void Cookie_has_expired_when_expiration_date_in_past()
{
var clock = new TestClock(new DateTime(2017, 06, 11));
Assert.IsTrue(new Cookie { ExpirationDate = new DateTime(2017, 06, 10) }.HasExpired(clock));
}
}