Interval timer (testing) — System Clock stack
These xUnit examples drive interval timers under a virtual PrimeTestClock. Callbacks fire only when the test advances the clock, so the assertions are deterministic and there are no real-world wait times.
/// <summary>
/// Verifies a one-shot interval timer fires after virtual time advances past the initial delay.
/// </summary>
[Fact]
public void IntervalTimer_AdvanceOneShot_FiresOnce ()
{
IPrimeTestClock clock = new PrimeTestClock(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero));
int fired = 0;
using IClockIntervalTimer registration = clock.RegisterTimer(
TimeSpan.FromMinutes(5),
Timeout.InfiniteTimeSpan,
_ => fired++,
CancellationToken.None);
clock.Advance(TimeSpan.FromMinutes(4));
fired.Should().Be(0);
clock.Advance(TimeSpan.FromMinutes(1));
fired.Should().Be(1);
registration.IsRepeating.Should().BeFalse();
}
/// <summary>
/// Verifies a repeating interval timer advances its schedule deterministically under
/// <see cref="IPrimeTestClock.Advance(System.TimeSpan)"/>.
/// </summary>
[Fact]
public void IntervalTimer_AdvanceRepeating_FiresOnEachInterval ()
{
IPrimeTestClock clock = new PrimeTestClock(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero));
int fired = 0;
using IClockIntervalTimer registration = clock.RegisterTimer(
TimeSpan.FromMinutes(2),
TimeSpan.FromMinutes(3),
_ => fired++,
CancellationToken.None);
clock.Advance(TimeSpan.FromMinutes(2));
fired.Should().Be(1);
clock.Advance(TimeSpan.FromMinutes(3));
fired.Should().Be(2);
registration.IsRepeating.Should().BeTrue();
}
/// <summary>
/// Verifies an asynchronous interval callback completes when virtual time reaches the due instant.
/// </summary>
[Fact]
public void IntervalTimer_RegisterAsyncTimer_Advance_CompletesCallback ()
{
IPrimeTestClock clock = new PrimeTestClock(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero));
int fired = 0;
using IClockIntervalTimer registration = clock.RegisterAsyncTimer(
TimeSpan.FromSeconds(1),
Timeout.InfiniteTimeSpan,
async (_, cancellationToken) =>
{
fired++;
await Task.Delay(0, cancellationToken).ConfigureAwait(false);
},
CancellationToken.None);
clock.Advance(TimeSpan.FromSeconds(1));
fired.Should().Be(1);
registration.State.Should().Be(TimerState.Completed);
}
What to notice
PrimeTestClockis constructed from aDateTimeOffsetso the BCL stack starts at a known UTC instant.Advance(TimeSpan)moves virtual time forward; callbacks scheduled at or before the new "now" are dispatched synchronously by the test clock beforeAdvancereturns.- A one-shot timer is requested by passing
Timeout.InfiniteTimeSpanas the repeat interval.IClockIntervalTimer.IsRepeatingisfalsefor one-shot registrations. - For repeating timers, advancing past each multiple of the repeat interval produces one callback per interval crossed.
- The async overload (
RegisterAsyncTimer) completes the awaited callback beforeAdvancereturns.
Related
- Interval timer (production) — wall-clock counterpart in the example app.
- Test-clock control APIs —
Set*,Advance,RunFor,Start/Stop, marching, and automatic runner semantics for both stacks. - API:
IPrimeTestClock·PrimeTestClock