Time abstraction in .NET 8

04/06/2023
.NET 8.NETC#

With the upcoming release of .NET, the team introduced an abstraction of time itself. That can bring you major benefits especially if you have to test scenarios where time is a crucial part! Until now, you had to create your own wrapper. This, of course, makes integration with 3rd party libraries tricky.

Your custom wrapper

Until now, you had to rely on your own wrapper to make time somewhat predictable in your tests. If you had ever experienced like: DateTime.UtcNow or friends that can become a pain during your tests. What people, including me, did:

public interface ITimeProvider
{
    DateTime UtcNow { get; }
}

public class TimeProvider : ITimeProvider
{
    public DateTime UtcNow => DateTime.UtcNow;
}

That is nice and works, but it seems odd to do this everytime again. Also that doesn't work with 3rd party libraries that use DateTime.UtcNow directly. You can't mock that easily.

TimeProvider

The new abstract classes build a base on which you can build your own time provider. It has a SystemTimeProvider that is the default implementation.

Imagine you have a function that does something like this await Task.Delay(TimeSpan.FromSeconds(5));, well obvious you don't want to wait 5 seconds in your tests. So Microsoft added new overloads for many APIs that have a time component:

public static Task Delay(TimeSpan delay, TimeProvider provider, CancellationToken cancellationToken = default);

So we have the chance to say how time flows in our tests! Let's see how we can use that:

var timer = new NoWaitingTimeProvider();

Console.WriteLine("Entering Task.Delay at " + timer.GetUtcNow());
await Task.Delay(TimeSpan.FromSeconds(5), timer);
Console.WriteLine("Exiting Task.Delay at " + timer.GetUtcNow());

public class NoWaitingTimeProvider : TimeProvider
{
    public override ITimer CreateTimer(
        TimerCallback callback,
        object? state,
        TimeSpan dueTime,
        TimeSpan period)
    {
        return base.CreateTimer(callback, state, TimeSpan.Zero, period);
    }
}

In this case we create a TimeProvider that has no dueTime or better the dueTime is set to TimeSpan.Zero so whatever we pass in, the timer is directly done! This is a very simple example, but you can imagine that you can do a lot more with this. Imagine you want to tests your retry logic, you can now control how time flows making your tests more reliable and faster!

And of course, the example I showed above is also manageable with the new TimeProvider as it offers a GetUtcNow method that you can override.

There is more

I showed a version where I implemented my own version of the TimeProvider but of course, you don't have to do that! TimeProvider is an abstract class where every function is virtual making it a prime candidate for mocking frameworks. So you can easily mock every aspect!

Conclusion

The new TimeProvider is a great addition to the .NET framework. It makes testing scenarios where time is a crucial part a lot easier and more reliable.

5
An error has occurred. This application may no longer respond until reloaded. Reload x