DEV Community

Cover image for Bending DateTime in .NET to Test Your Code Better
Stephen Walsh
Stephen Walsh

Posted on • Updated on

Bending DateTime in .NET to Test Your Code Better

Okay I'll dial down the hyperbole, over my too many years (read I'm feeling old) as an engineer I have run into a problem where my code has contained a DateTime.Now or DateTime.UtcNow. When I write a test, I can't validate the actual time because milliseconds have passed from when the code ran and then my test goes to validate. It's not a huge problem but annoying as I like to validate everything to make sure I'm not accidentally manipulating those values somewhere else.

There is an easy solution to this, and before I detail out the solution that I used, I need to call out the inspirations for this. The TL;DR is that you can consume my SimpleDateTimeProvider NuGet Package to help you solve this. The implementation of the code for this lies below.


The Solution

I have created a DateTimeProvider consisting of an interface, and two implementations of the interface. One implementation returns the System values and the other returns Mocked values that are preset by the user.

The Interface

public interface IDateTimeProvider
{
    DateTime Now { get; }
    DateTime Today { get; }
    DateTime UtcNow { get; }
}
Enter fullscreen mode Exit fullscreen mode

The System Implementation

public class SystemDateTimeProvider : IDateTimeProvider
{
    public DateTime Now => DateTime.Now;
    public DateTime Today => DateTime.Today;
    public DateTime UtcNow => DateTime.UtcNow;
}
Enter fullscreen mode Exit fullscreen mode

The Mock Implementation

public class MockDateTimeProvider : IDateTimeProvider
{
    public DateTime Now
    {
        get => this.now.ThrowIfNotSet(DateTimeType.Now);
        set => this.now = value;
    }
    public DateTime Today
    {
        get => this.today.ThrowIfNotSet(DateTimeType.Today);
        set => this.today = value;
    }
    public DateTime UtcNow
    {
        get => this.utcNow.ThrowIfNotSet(DateTimeType.UtcNow);
        set => this.utcNow = value;
    }
}
Enter fullscreen mode Exit fullscreen mode

Bending Date and Time

This is a simple solution and it's easy to get under way using the providers, simply inject the system provider under the IDateTimeProvider interface in your functional code. If you are using another library, you'll know the syntax but follow the same formula.

_ = services.AddSingleton<IDateTimeProvider, SystemDateTimeProvider>();
Enter fullscreen mode Exit fullscreen mode

Next step is to create your class and use that registered SystemDateTimeProvider that we just created via the IDateTimeProvider interface. Then use the provider to set the DateTime values in your class.

public class Service
{
    private readonly IDateTimeProvider dateTimeProvider;

    public Service(IDateTimeProvider dateTimeProvider)
    {
        this.dateTimeProvider = dateTimeProvider;
    }

    public string DateTimeNow()
    {
        return $"DateTime.Now is {this.dateTimeProvider.Now}";
    }
}
Enter fullscreen mode Exit fullscreen mode

The whole purpose of this was to allow for testable code. So now that you have your class above, you can inject the MockDateTimeProvider in its place to control the DateTime values in your tests. The following example shows how to write a test in XUnit, using Shouldly for assertion.

[Fact]
public void Today_ShouldReturn_MockedToday()
{
    // Arrange
    var provider = new MockDateTimeProvider();
    var service = new Service(provider);
    var today = DateTime.Today;

    provider.Today = today;

    // Act
    var result = service.DateTimeToday();

    // Assert
    _ = result.ShouldBeOfType<string>();
    result.ShouldBe($"DateTime.Today is {today}");
}
Enter fullscreen mode Exit fullscreen mode

Where Can I Find This?

All of this open sourced. You can find my work on GitHub
at SimpleDateTimeProvider Repository and the published package at SimpleDateTimeProvider NuGet.


Support

If you like this, checkout my other examples on GitHub and consider supporting me at Buy Me a Coffee.

"Buy Me A Coffee"

Discussion (6)

Collapse
ramenturismo profile image
Emy F. (She/Her) • Edited on

I was awaiting for "Using .NET 5/6 and source generators...", not a classic solution like this, not going to lie!

Collapse
stphnwlsh profile image
Stephen Walsh Author

Yeah sorry, not everything is earth shattering. I'm interested in your source generators solution though.

Collapse
ramenturismo profile image
Emy F. (She/Her)

I just meant what you just said : A earth shattering solution. Did not meant as an offense. I'm glad this kind of article exists because I've seen a lot of dev not taking this into account, so thank you!

BTW; I don't think SourceGenerators are meant for this, although we could achieve this by generating a property with the interface you created there, but it would go against constructor injection.
I never used Source Generators, could be interesting to dig into this (Found a "cookbook").

Thread Thread
stphnwlsh profile image
Stephen Walsh Author

Haha just read my comment back, reads a little snarky to me....sorry about that.

As for source generators, your comment got me thinking about how I could use them. Pretty sure they wouldn't work well for something like this but loads of other applications. Thanks for making me ponder something new!!!

Collapse
misterjunio profile image
Júnio Silva

Obligatory review comment from me saying that the test method name should start with Today_ rather than Now_ haha

Love your work 😍

Collapse
stphnwlsh profile image
Stephen Walsh Author

Hahahahaha perfect!!!! Update on the way!