Better time-dependent testing with TimeProvider in .NET 8

Better time-dependent testing with TimeProvider in .NET 8

Previously in .NET you might have had a class like this that you would inject in your classes that needed a predictable time in your unit tests:

public interface IMyDateTime
{
    public DateTime Now();
    public DateTime UtcNow();
}

public class MyDateTime : IMyDateTime
{
    public DateTime Now() => DateTime.Now;
    public DateTime UtcNow() => DateTime.UtcNow;
}

Wiring it up in the dependency injection:

builder.Services.AddSingleton<IMyDateTime, MyDateTime>();

And then, for instance, inject it into a service:

public class MyService(IMyDateTime myDateTime)
{
    public DateTime DoSomething()
    {
        var currentLocalTime = myDateTime.Now();
        return currentLocalTime;
    }
}

Then in the unit test of the service, you could replace the implementation of IMyDateTime with a an instance with a hard-coded value:

[TestClass]
public class MyServiceTest
{
    [TestMethod]
    public void DoSomething_DefaultCase_ReturnsLocalTime()
    {
        // Arrange
        var myDateTime = new FakeDateTime(new DateTime(2025, 07, 10));
        var service = new MyService(myDateTime);

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

        // Assert
        Assert.AreEqual(new DateTime(2025, 07, 10), result);
    }
}

public class FakeDateTime(DateTime currentLocalTime) : IMyDateTime
{
    public DateTime Now() => currentLocalTime;
    public DateTime UtcNow() => currentLocalTime.ToUniversalTime();
}

However, in .NET 8, there’s now a class called TimeProvider. And for testing you can use FakeTimeProvider. Note though that FakeTimeProvider is in the NuGet Package Microsoft.Extensions.TimeProvider.Testing.

Using TimeProvider, you don’t have to create any custom interface and class anymore. Instead, you can change your service to the following:

public class MyService(TimeProvider timeProvider)
{
    public DateTime DoSomething()
    {
        var currentLocalTime = timeProvider.GetLocalNow().LocalDateTime;
        return currentLocalTime;
    }
}

Register TimeProvider in the dependency injection:

builder.Services.AddSingleton(TimeProvider.System);

And do the unit test:

[TestClass]
public MyServiceTest
{
    [TestMethod]
    public void DoSomething_DefaultCase_ReturnsLocalTime()
    {
        // Arrange
        var timeProvider = new FakeDateTime(new DateTime(2025, 07, 10));
        var service = new MyService(timeProvider);

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

        // Assert
        Assert.Equals(result, new DateTime(2025, 07, 10));
    }
}

Looks really clean!