Skip to main content

Testing

TimeProvider

TimeProvider is an abstract base class introduced in .NET 8 that provides a standard way to access time-related information.

Key Features:

  • Standardization: TimeProvider provides a unified approach to accessing time-related information, reducing the need for various custom implementations scattered across the codebase.
  • Testability: By using a TimeProvider, time-dependent code can be easily tested. Developers can inject mock or custom implementations of TimeProvider to simulate different times and durations in unit tests.
  • Flexibility: TimeProvider can be extended to create custom time providers that suit specific needs, such as simulating time in different time zones or providing consistent time sources in distributed systems.
  • Separation of Concerns: It separates the concerns of obtaining the current time from business logic, making the code cleaner and more maintainable.
  1. Create a new test project

    dotnet new xunit -o TimeProviderTestProject
    cd TimeProviderTestProject
  2. Add the necessary packages for testing

     dotnet add package xunit
    dotnet add package xunit.runner.visualstudio
    dotnet add package Microsoft.Extensions.TimeProvider.Testing
    dotnet add package Microsoft.Extensions.Diagnostics.Testing
    dotnet add package AwesomeAssertions
  3. Domain - Create a Task object

    public class Task
    {
    public required string Name { get; set; }
    public DateTimeOffset DueDate { get; set; }

    public bool IsOverdue()
    {
    var isOverdue = DueDate < TimeProvider.GetUtcNow();

    return isOverdue;
    }

    public required TimeProvider TimeProvider { get; init; }
    }
  4. Next, create a test using FakeTimeProvider

    public class TimeProviderTests
    {
    [Fact]
    public void TimeProviderTest_TaskIsOverdue_ReturnsFalse_WhenDueDateIsInFuture_Then_ReturnsTrue_WhenDueDateIsInPast()
    {
    var timeProvider = new FakeTimeProvider();

    var task = new Task
    {
    Name = "Future Task",
    DueDate = timeProvider.GetUtcNow().AddDays(1),
    TimeProvider = timeProvider,
    };

    task.IsOverdue().Should().BeFalse();

    timeProvider.Advance(TimeSpan.FromDays(5));

    task.IsOverdue().Should().BeTrue();
    }
    }
    note

    FakeTimeProvider allows us to shift the tests perspective of time, making it easy to test time-dependent logic.

FluentAssertions AwesomeAssertions is a library that provides a more readable and expressive way to write assertions in tests. It enhances the readability of test code by allowing developers to write assertions in a natural language style.

  1. Run the tests - see they pass

FakeLogger

Microsoft.Extensions.Logging.Testing provides a way to create fake loggers for testing purposes. This allows developers to verify that logging occurs as expected without needing to set up a real logging infrastructure.

  1. Add Logger to the task and update the method to log a warning if the task is overdue

    public class Task
    {
    public required string Name { get; set; }
    public DateTimeOffset DueDate { get; set; }

    public bool IsOverdue()
    {
    var isOverdue = DueDate < TimeProvider.GetUtcNow();
    if (isOverdue)
    {
    Logger?.LogWarning("Task is overdue.");
    }

    return isOverdue;
    }

    public required TimeProvider TimeProvider { get; init; }
    public ILogger<Task>? Logger { get; init; }
    }
  2. Add a test to verify the logging functionality

     [Fact]
    public void TimeProviderTest_TaskIsOverdue_LogsWarning()
    {
    var timeProvider = new FakeTimeProvider();
    var logger = new FakeLogger<Task>();

    var task = new Task
    {
    Name = "Overdue Task",
    DueDate = timeProvider.GetUtcNow().AddDays(1),
    TimeProvider = timeProvider,
    Logger = logger
    };

    task.IsOverdue();
    logger.Collector.Count.Should().Be(0);

    timeProvider.Advance(TimeSpan.FromDays(5));
    task.IsOverdue();

    logger.Collector.Count.Should().Be(1);
    logger.LatestRecord.Message.Should().Be("Task is overdue.");
    }
  3. Show the tests pass

Using, ILogger and FakeLogger allows us to test the logging functionality without needing to set up a real logging infrastructure - our code is testable out of the box.