DEV Community

mohamed Tayel
mohamed Tayel

Posted on

Using TimeProvider for Easier Unit Testing in .NET 8

Introduction

In .NET 8, Microsoft introduced TimeProvider, a new feature that simplifies testing time-based logic. Previously, mocking time often required third-party libraries or custom wrappers around DateTime. This article provides a step-by-step guide to using TimeProvider for more effective unit testing.

What is TimeProvider?

TimeProvider is an abstract class that allows developers to override how time is provided in their applications. This enables better control over time for testing purposes. The default implementation, TimeProvider.System, uses the system clock, but you can easily create custom implementations.

Example Scenario: Time-Based Greetings

We'll implement a service that provides greetings based on the current time, e.g., "Morning," "Afternoon," "Evening," and "Night."

Step 1: Create the Time-Based Service

Create a service class that categorizes greetings according to the time of day using TimeProvider:

// UsingTime/TimeOfDayService.cs
namespace UsingTime
{
    public class TimeOfDayService
    {
        private readonly TimeProvider _timeProvider;

        public TimeOfDayService(TimeProvider timeProvider)
        {
            _timeProvider = timeProvider;
        }

        public string GetTimeOfDay()
        {
            var currentTime = _timeProvider.GetLocalNow();

            return currentTime.Hour switch
            {
                <= 6 => "Night",
                > 6 and <= 12 => "Morning",
                > 12 and <= 18 => "Afternoon",
                > 18 and <= 24 => "Evening",
                _ => "Invalid hour"
            };
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create Custom Time Providers

Implement custom TimeProvider classes to simulate different times of day for testing purposes:

// UsingTimeTest/NightTimeProvider.cs
namespace UsingTimeTest
{
    public class NightTimeProvider : TimeProvider
    {
        public override DateTimeOffset GetUtcNow()
        {
            return new DateTimeOffset(2023, 12, 1, 1, 0, 0, TimeSpan.Zero); // 1 AM
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Repeat this for other times:

// UsingTimeTest/MorningTimeProvider.cs
namespace UsingTimeTest
{
    public class MorningTimeProvider : TimeProvider
    {
        public override DateTimeOffset GetUtcNow()
        {
            return new DateTimeOffset(2023, 12, 1, 8, 0, 0, TimeSpan.Zero); // 8 AM
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
// UsingTimeTest/AfternoonTimeProvider.cs
namespace UsingTimeTest
{
    public class AfternoonTimeProvider : TimeProvider
    {
        public override DateTimeOffset GetUtcNow()
        {
            return new DateTimeOffset(2023, 12, 1, 16, 0, 0, TimeSpan.Zero); // 4 PM
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
// UsingTimeTest/EveningTimeProvider.cs
namespace UsingTimeTest
{
    public class EveningTimeProvider : TimeProvider
    {
        public override DateTimeOffset GetUtcNow()
        {
            return new DateTimeOffset(2023, 12, 1, 20, 0, 0, TimeSpan.Zero); // 8 PM
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Write Unit Tests

Write unit tests to verify the TimeOfDayService using these custom time providers.

// UsingTimeTest/TimeOfDayServiceTests.cs
using Xunit;
using UsingTime;

namespace UsingTimeTest
{
    public class TimeOfDayServiceTests
    {
        private TimeOfDayService _timeOfDayService;
        private TimeProvider _timeProvider;

        [Fact]
        public void TimeOfDay_ShouldReturnMorning_WhenItsMorning()
        {
            _timeProvider = new MorningTimeProvider();
            _timeOfDayService = new TimeOfDayService(_timeProvider);

            var result = _timeOfDayService.GetTimeOfDay();
            Assert.Equal("Morning", result);
        }

        [Fact]
        public void TimeOfDay_ShouldReturnAfternoon_WhenItsAfternoon()
        {
            _timeProvider = new AfternoonTimeProvider();
            _timeOfDayService = new TimeOfDayService(_timeProvider);

            var result = _timeOfDayService.GetTimeOfDay();
            Assert.Equal("Afternoon", result);
        }

        [Fact]
        public void TimeOfDay_ShouldReturnEvening_WhenItsEvening()
        {
            _timeProvider = new EveningTimeProvider();
            _timeOfDayService = new TimeOfDayService(_timeProvider);

            var result = _timeOfDayService.GetTimeOfDay();
            Assert.Equal("Evening", result);
        }

        [Fact]
        public void TimeOfDay_ShouldReturnNight_WhenItsNight()
        {
            _timeProvider = new NightTimeProvider();
            _timeOfDayService = new TimeOfDayService(_timeProvider);

            var result = _timeOfDayService.GetTimeOfDay();
            Assert.Equal("Night", result);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

With TimeProvider in .NET 8, mocking time-based behavior is now easier and more standardized. Instead of relying on third-party tools or custom abstractions, developers can use the built-in API to manipulate time in unit tests. This results in more reliable and maintainable tests, simplifying the development process.

Top comments (1)

Collapse
 
moh_moh701 profile image
mohamed Tayel