- Home
- .NET tutorials
- TimeProvider makes it easier to mock time in .NET 8
TimeProvider makes it easier to mock time in .NET 8
Published: Friday 10 November 2023
The TimeProvider class has been introduced in .NET 8 which provides abstractions to mock time and create a timer.
FakeTimeProvider class that allows us to set the current UTC time and local time zone, we can use it to write unit tests that involve a time and a timer.
The TimeProvider class
The TimeProvider class provides two abstractions that we can use to get the current UTC time and local time zone.
GetUtcNow and LocalTimeZone properties have been marked with the virtual keyword meaning that they can be overridden in a child class.
GetLocalNow method, we can get the local time as it's set in the TimeProvider instance.
public abstract class TimeProvider
{
...
public virtual DateTimeOffset GetUtcNow() => DateTimeOffset.UtcNow;
public DateTimeOffset GetLocalNow()
{
...
}
public virtual TimeZoneInfo LocalTimeZone => TimeZoneInfo.Local;
...
}
The FakeTimeProvider class
The FakeTimeProvider class which inherits the TimeProvider class provides us with the SetUtcNow and SetLocalTimeZone methods to allow us to set the UTC time and local time zone.
Mock time with the FakeTimeProvider class
Once added, we can start writing some unit tests and mock the time.
Writing unit tests to mock and test the time
We are going to create a MyCalendar class. This will provide an instance of the TimeProvider class in the constructor where the reference will be stored as a private readonly field.
IsItWednesday method. This will check the current local time to see if the day of the week is currently Wednesday.
public class MyCalendar
{
private readonly TimeProvider _timeProvider;
public MyCalendar(TimeProvider timeProvider)
{
_timeProvider = timeProvider;
}
public bool IsItWednesday()
{
return _timeProvider.GetLocalNow().DayOfWeek == DayOfWeek.Wednesday;
}
}
With that done, we are going to write some unit tests in xUnit. The first is to mock the time so the date is set to a Sunday.
FakeTimeProvider class, call the SetUtcNow and SetLocalTimeZone methods, and then create a new MyCalendar instance, passing in the FakeTimeProvider instance as the constructors parameter.
IsItWednesday method in the MyCalendar class. As the date is a Sunday, we expect it to return false.
[Fact]
public void MockSunday_CallIsItWednesday_ShouldReturnFalse()
{
// Arrange
var fakeTimeProvider = new FakeTimeProvider();
// Set the current UTC time
fakeTimeProvider.SetUtcNow(new DateTime(2023, 11, 5));
fakeTimeProvider.SetLocalTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Greenwich Standard Time"));
// Act
var result = new MyCalendar(fakeTimeProvider);
Assert.False(result.IsItWednesday());
}
We can do a similar check to ensure that the IsItWednesday method returns true if the date is set to Wednesday.
[Fact]
public void MockWednesday_CallIsItWednesday_ShouldReturnTrue()
{
// Arrange
var fakeTimeProvider = new FakeTimeProvider();
// Set the current UTC time
fakeTimeProvider.SetUtcNow(new DateTime(2023, 11, 8));
fakeTimeProvider.SetLocalTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Greenwich Standard Time"));
// Act
var result = new MyCalendar(fakeTimeProvider);
Assert.True(result.IsItWednesday());
}
Mock a timer
The TimeProvider abstract class also provides a method that allows us to create a timer:
public abstract class TimeProvider
{
...
public virtual ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period)
{
...
}
...
}
Combined with the FakeTimeProvider class, we are also able to write a unit test when the callback is made.
Callback class. Within it, we are going to provide a TimeProvider instance in the constructor and create the timer.
HasCalledBack property to true once the timer's callback has been invoked.
public class Callback
{
public bool HasCalledBack { get; private set; }
public Callback(TimeProvider timeProvider)
{
timeProvider.CreateTimer(_ => HasCalledBack = true, this, TimeSpan.FromSeconds(20), Timeout.InfiniteTimeSpan);
}
}
With that, we can write the xUnit unit test. Like with the previous tests, we will create a new FakeTimeProvider instance and set the current UTC time and local time zone.
FakeTimeProvider instance into a new instance of the Callback class as the constructor's parameter.
[Fact]
public void Timer_InvokesCallback_SetsCallbackToTrue()
{
var fakeTimeProvider = new FakeTimeProvider();
var date = new DateTime(2023, 11, 8);
fakeTimeProvider.SetUtcNow(date);
fakeTimeProvider.SetLocalTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Greenwich Standard Time"));
var callback = new Callback(fakeTimeProvider);
Assert.False(callback.HasCalledBack);
fakeTimeProvider.SetUtcNow(date.AddSeconds(30));
Assert.True(callback.HasCalledBack);
}
Initially, we expect the HasCalledBack property to still be false and we can set an assertion for that.
FakeTimeProvider instance.
HasCalledBack property, but this time, we expect it to return true.
Watch the video
Watch our video where we talk about the TimeProvider abstract class and how we can add the FakeTimeProvider class to our xUnit test project.
In-addition, there is also a code example that can be downloaded which uses the same project featured in this tutorial.
Related pages