Bending DateTime in .NET to Test Your Code Better
Tags: dotnet, testing, validation, nuget
Published: 2022-01-19 02:13:16

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](https://github.com/stphnwlsh/SimpleDateTimeProvider#inspiration) for this.  The TL;DR is that you can consume my [SimpleDateTimeProvider NuGet Package](https://www.nuget.org/packages/SimpleDateTimeProvider/) 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; }
}
```

### The System Implementation
```
public class SystemDateTimeProvider : IDateTimeProvider
{
    public DateTime Now => DateTime.Now;
    public DateTime Today => DateTime.Today;
    public DateTime UtcNow => DateTime.UtcNow;
}
```

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

---

## 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>();
```

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

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](https://xunit.net/), using [Shouldly](https://shouldly.io/) 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}");
}
```

---

## Where Can I Find This?
All of this open sourced.  You can find my work on GitHub 
at [SimpleDateTimeProvider Repository](https://github.com/stphnwlsh/SimpleDateTimeProvider) and the published package at [SimpleDateTimeProvider NuGet](https://www.nuget.org/packages/SimpleDateTimeProvider/).

---

## Support

If you like this, checkout my other examples on [GitHub](https://github.com/stphnwlsh) and consider supporting me at [Buy Me a Coffee](https://www.buymeacoffee.com/stphnwlsh).

[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/guidelines/download-assets-sm-1.svg)](https://www.buymeacoffee.com/stphnwlsh)