Skip to content

Testing

Test utilities for deterministic time handling.

Tests that depend on DateTime.Now are:

  • Non-deterministic
  • Hard to reproduce
  • Flaky around midnight, month boundaries, etc.

Never use DateTime.Now directly. Use IClock:

public interface IClock
{
DateTimeOffset UtcNow { get; }
LocalDate Today { get; }
}
// Register system clock
services.AddSingleton<IClock, SystemClock>();
// Use TestClock for deterministic time
var clock = new TestClock();
clock.SetDateTime(2024, 6, 15, 10, 0, 0);
var clock = new TestClock();
// Set to specific date/time
clock.SetDateTime(2024, 6, 15, 10, 30, 0);
// Set from DateTimeOffset
clock.SetDateTime(new DateTimeOffset(2024, 6, 15, 10, 30, 0, TimeSpan.Zero));
var clock = new TestClock();
clock.SetDateTime(2024, 6, 15, 10, 0, 0);
// Advance by duration
clock.Advance(TimeSpan.FromHours(2));
// Now: 2024-06-15 12:00:00
// Advance by days
clock.AdvanceDays(1);
// Now: 2024-06-16 12:00:00
var clock = new TestClock();
clock.Freeze(new DateTimeOffset(2024, 6, 15, 10, 0, 0, TimeSpan.Zero));
// Multiple reads return same time
var t1 = clock.UtcNow;
await Task.Delay(100);
var t2 = clock.UtcNow;
t1.Should().Be(t2); // Same instant

Pre-configured context for common test scenarios:

// Rome timezone context
var context = TestTemporalContext.ForRome();
context.Culture // "it-IT"
context.TimeZone // "Europe/Rome"
context.Today // LocalDate in Rome
// New York context
var nyContext = TestTemporalContext.ForNewYork();
// Custom context
var custom = TestTemporalContext.Create("de-DE", "Europe/Berlin");

Holiday provider for tests:

var holidays = new TestHolidayProvider()
.AddHoliday(2024, 12, 25, "Christmas")
.AddHoliday(2024, 12, 26, "Boxing Day");
var calculator = new TemporalCalculator(holidays);
public class SubscriptionServiceTests
{
private readonly TestClock _clock;
private readonly SubscriptionService _service;
public SubscriptionServiceTests()
{
_clock = new TestClock();
_service = new SubscriptionService(_clock);
}
[Fact]
public void Subscription_ExpiresAfter30Days()
{
// Arrange
_clock.SetDateTime(2024, 1, 1, 0, 0, 0);
var subscription = _service.Create();
// Act - advance 30 days
_clock.AdvanceDays(30);
// Assert
subscription.IsExpired(_clock).Should().BeTrue();
}
[Fact]
public void Subscription_ValidBefore30Days()
{
// Arrange
_clock.SetDateTime(2024, 1, 1, 0, 0, 0);
var subscription = _service.Create();
// Act - advance 29 days
_clock.AdvanceDays(29);
// Assert
subscription.IsExpired(_clock).Should().BeFalse();
}
}
[Fact]
public void DisplaysTimeInUserTimezone()
{
// Arrange
var clock = new TestClock();
clock.SetDateTime(2024, 6, 15, 14, 0, 0); // 2 PM UTC
// Act
var romeTime = ZonedDateTime.FromUtc(clock.UtcNow, "Europe/Rome");
var nyTime = ZonedDateTime.FromUtc(clock.UtcNow, "America/New_York");
// Assert
romeTime.LocalDateTime.Hour.Should().Be(16); // 4 PM
nyTime.LocalDateTime.Hour.Should().Be(10); // 10 AM
}
[Fact]
public void HandlesDstSpringForward()
{
// March 10, 2024 - US DST spring forward at 2 AM
var clock = new TestClock();
clock.SetDateTime(2024, 3, 10, 6, 0, 0); // 6 AM UTC
var eastern = ZonedDateTime.FromUtc(clock.UtcNow, "America/New_York");
// Should be 2 AM EDT (not 1 AM EST)
eastern.Offset.Should().Be(TimeSpan.FromHours(-4)); // EDT
}
[Fact]
public void AddBusinessDays_SkipsChristmas()
{
// Arrange
var holidays = new TestHolidayProvider()
.AddHoliday(2024, 12, 25, "Christmas");
var calculator = new TemporalCalculator(holidays);
// Tuesday Dec 24
var start = new LocalDate(2024, 12, 24);
// Act - add 1 business day
var result = calculator.AddBusinessDays(start, 1);
// Assert - should skip Christmas, land on Dec 26
result.Should().Be(new LocalDate(2024, 12, 26));
}
// Production setup
services.AddSingleton<IClock, SystemClock>();
services.AddScoped<ITemporalContext, TemporalContext>();
// Test setup
services.AddSingleton<IClock>(new TestClock());
services.AddScoped<ITemporalContext, TestTemporalContext>();
  1. Never use DateTime.Now - Always inject IClock
  2. Freeze time in tests - Avoid flaky tests from time passing during execution
  3. Test boundary conditions - Midnight, month/year boundaries, DST transitions
  4. Test with realistic data - Use real timezone names, realistic dates