Testing around DateTime.Now

Posted by on Thu, May 30, 2024

Spend any time writing code and unit tests and you’ll quickly learn to hate using DateTime.Now and DateTime.UtcNow in your code because it makes testing a bear, resulting in creating the same boiler plate code in every project. Well, starting with .NET 8 you can kiss that boilerplate code goodbye!

What’s wrong with using DateTime.Now?

The DateTime type is a read-only struct and the Now property is only a getter. Because of this, we cannot use traditional mocking approaches to set a fake value for the return value of Now.

Up until now the most common approach, highlighted in many a book/article/course on unit testing, was to create a wrapper class that you would use through dependency injection instead of directly calling DateTime.Now. For example:

 1public interface IProvideTime
 2{
 3    public DateTime Now { get; }
 4    public DateTime GetUtcNow { get; }
 5}
 6
 7public class CusomtTimeProvider : IProvideTime
 8{
 9    public DateTime Now => DateTime.Now;
10    public DateTime UtcNow => DateTime.UtcNow;
11}
12
13// register during startup
14builder.Services.AddSingleton<IProvideTime, CustomTimeProvider>();
15
16// and inject into any classes that need the current DateTime
17public class MessengerService(IProvideTime timeProvider)
18{
19    public void SendMessage(string message)
20    {
21        DateTime messageSent = timeProvider.UtcNow;
22        // now use it...
23    }
24}

This allowed us to write unit tests for any code that needs to grab the current moment in time in order to make decisions, like the price of an item before, during, and after a sale.

1// unit testing code
2ITimeProvider _fakeTimeProvider = Substitute.For<IProvideTime>();
3_fakeTimeProvider.Now.Returns(new DateTime(2024, 05, 01, 15, 0, 0));
4
5MessengerService svc = new MessengerService(_fakeTimeProvider);
6svc.SendMessage("Hello!");

Almost everyone needed to write this type of wrapper for almost every project that they worked on. Even though it is very simple, it was so common that it always felt like this should have been baked into the framework a long time ago.

Enter the TimeProvider class in .NET 8!

Instead of injecting a custom interface into our classes, we can now inject a TimeProvider instance and use either the GetLocalNow() or GetUtcNow() methods to return either the local date/time or UTC date/time respectively.

Our example above now becomes:

 1// register during startup
 2builder.Services.AddSingleton(TimeProvider.System);
 3
 4// and inject into any classes that need the current DateTime
 5public class MessengerService(IProvideTime timeProvider)
 6{
 7    public void SendMessage(string message)
 8    {
 9        DateTime messageSent = timeProvider.UtcNow;
10        // now use it...
11    }
12}

When it comes to the unit tests, this also becomes much simpler because Microsoft has provided a FakeTimeProvider class that inherits from the TimeProvider class. Now you just need to call the SetUtcNow method to set the DateTime value that you need for your test

 1// code
 2public class MessengerService(TimeProvider timeProvider)
 3{
 4    public void SendMessage(string message)
 5    {
 6        DateTime messageSent = timeProvider.GetUtcNow();  // .GetLocalNow()
 7        // now use it...
 8    }
 9}
10
11// startup
12builder.Services.AddSingleton(TimeProvider.System);
13
14// unit test code
15FakeTimeProvider _timeProvider = new FakeTimeProvider();
16_timeProvider.SetUtcNow(new DateTime(2024, 05, 01, 15, 0, 0));
17
18MessengerService svc = new MessengerService(_fakeTimeProvider);
19svc.SendMessage("Hello!")

Finally, no more boilerplate code needed in every new project!