Define the observable contract
A time-dependent rule should receive an observation source when tests need to control its boundary. TimeProvider is part of modern .NET and provides a standard abstraction for obtaining time and creating supported timers. This .NET9 sample derives a small ManualClock that overrides only GetUtcNow. It does not implement an entire virtual-time scheduler. That narrow scope is deliberate: the deadline rule needs a wall-clock instant, while timer completion and elapsed-time measurement are different capabilities that should not be silently claimed just because a TimeProvider subclass exists.
Walk through the real program
The prepared start is midnight UTC on January 1, 2026, and the deadline is one minute later. Due compares the clock's current UTC instant with that deadline using an inclusive greater-than-or-equal rule. Before advancing, the first output is before:False. Advance adds exactly one prepared minute to the stored instant, so the second output is at:True. No real waiting occurs and the device's current clock is never read by this overridden method. The result is deterministic because the test controls the observation, not because a sleep happened to last long enough.
Why the design is testable
The dependency makes the boundary explicit. A domain service can accept TimeProvider in its constructor and call GetUtcNow at the point its policy needs an instant. Tests supply the controlled implementation, while ordinary application construction can supply an appropriate real provider. This is dependency injection through normal parameter passing; a framework container is optional. Use DateTimeOffset for an instant with an offset and keep the sample at UTC. A local appointment, a calendar date, and a monotonic elapsed duration require their own carefully chosen policies rather than interchangeable clock types.
A boundary that matters
Advancing this subclass does not complete Task.Delay, change GetTimestamp, or control every timer an application might start. Those capabilities require corresponding provider behavior and APIs that actually receive the provider. A fake wall clock cannot repair code that ignores it and reads DateTimeOffset.UtcNow directly. Also avoid one mutable global clock shared by unrelated tests, which can create order-dependent failures. Build a fresh clock for each independent scenario, state the equality boundary, and record which method the production code reads instead of assuming all uses of time were automatically intercepted.
Practice: isolate one behavior
First advance only fifty-nine seconds and predict that Due remains false. Then advance one additional second and predict true. Hint: compare typed instants rather than formatted minute strings; the rule includes the exact deadline. Use a fresh ManualClock for a separate after-deadline case and test that negative Advance is rejected by its explicit helper contract. Keep that helper validation separate from the deadline policy so a failing clock setup is not misdiagnosed as an incorrect business comparison.
Practice: challenge another assumption
For another exercise, move Due into a class receiving TimeProvider and a deadline through construction. Test before, exactly at, and after using independent instances. Hint: ensure the class reads its supplied provider rather than an ambient system clock. Then describe what would be necessary to test a provider-aware delay or recurring timer. Do not add a real sleep to pretend the current ManualClock controls it. Consult the supported TimeProvider testing APIs or a properly configured test utility when that broader scheduling contract becomes the subject.
Review the evidence
The baseline proves a controlled UTC observation and an inclusive expiration boundary. It does not benchmark time, emulate the operating system, or change every timer in the browser runtime. A reliable design separates the source of an observation from the rule that interprets it and from the mechanism that schedules work. That separation makes edge cases cheap and repeatable, while preserving honest limits about which provider capabilities were implemented. Record those limits in test helpers so future callers do not mistake a wall-clock fake for a complete virtual scheduler. A real wall clock can move after synchronization or administrative correction; monotonic elapsed-time measurement is a separate requirement that this forward-only helper deliberately does not model.
- The original deadline observations are False before and True exactly at.
- This subclass controls GetUtcNow and deliberately does not control timers.
- Fresh scenario clocks avoid shared mutable test state.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
var start = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
var clock = new ManualClock(start);
var deadline = start.AddMinutes(1);
bool Due() => clock.GetUtcNow() >= deadline;
Console.WriteLine($"before:{Due()}");
clock.Advance(TimeSpan.FromMinutes(1));
Console.WriteLine($"at:{Due()}");
sealed class ManualClock : TimeProvider
{
private DateTimeOffset now;
public ManualClock(DateTimeOffset initial) { now = initial; }
public override DateTimeOffset GetUtcNow() => now;
public void Advance(TimeSpan amount)
{
if (amount < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(amount));
now += amount;
}
}
Expected output
before:False at:True
This subclass controls GetUtcNow only; it does not implement virtual timers.