Use a testable (date) time provider

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

@ganncamp I saw added the C# tag. My PR already supports VB.NET, and I think this might be an issue in (some) other languages too?!

I can drop it if you like @Corniel (or you can edit behind me if you like :smiley:). I was just trying to get the right folks’ attention on it & that’s more likely to happen with the right tags. Some teams only monitor “their” tags.

 
Ann

I was just curious. This is fine with me. I did not tag c# (or .NET) as I suspect it to be a test that is not limited to .NET only, but C# is fine. :slight_smile: