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.