visit
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 to help you solve this. The implementation of the code for this lies below.
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.
public interface IDateTimeProvider
{
DateTime Now { get; }
DateTime Today { get; }
DateTime UtcNow { get; }
}
public class SystemDateTimeProvider : IDateTimeProvider
{
public DateTime Now => DateTime.Now;
public DateTime Today => DateTime.Today;
public DateTime UtcNow => DateTime.UtcNow;
}
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;
}
}
This is a simple solution and it's easy to get underway 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 , using 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}");
}
First published