Class ActiveTimerAssertions
- Namespace
- TimeAssertions.TUnit
- Assembly
- TimeAssertions.TUnit.dll
Fluent timer-leak assertions on an ObservableTimeProvider. The headline use case
is verifying that hosted-service code (BackgroundService / IHostedService ping
loops, heartbeats, debounce timers) disposes every ITimer it
starts: wrap a FakeTimeProvider in an ObservableTimeProvider, run the code
under test, then assert the active-timer set.
public static class ActiveTimerAssertions
- Inheritance
-
ActiveTimerAssertions
- Inherited Members
Remarks
On failure these assertions name the surviving timers by the schedule they carry
([dueTime=…, period=…]), so a leak is diagnosed by which timer remained rather
than by a bare integer count.
For an asynchronous disposal race (the timer is disposed on a background StopAsync that
has not completed when the assertion runs), use the polling overload
await Assert.That(time).HasNoActiveTimersEventually(timeout), which re-checks the live
active count on the real wall clock until it reaches zero or the timeout elapses. The
count-targeted siblings (HasActiveTimerCountEventually,
HasAtLeastActiveTimerCountEventually, HasAtMostActiveTimerCountEventually,
HasActiveTimersEventually) poll for other steady states.
To verify how many times a timer's callback ran, advance fake time on the inner provider and assert with HasTimerFiredCount(ObservableTimeProvider, int), HasNoTimerFired(ObservableTimeProvider), or HasTimerFiredAtLeast(ObservableTimeProvider, int). The fire count is cumulative across every timer the provider created and survives disposal.
Methods
HasActiveTimerCount(ObservableTimeProvider, int)
Asserts that exactly expected timers created through the
ObservableTimeProvider are currently active. Useful for the registration half
of a leak test ("the loop started its one timer") before the disposal half checks the count
returns to zero.
[GenerateAssertion(InlineMethodBody = false)]
public static AssertionResult HasActiveTimerCount(this ObservableTimeProvider value, int expected)
Parameters
valueObservableTimeProviderThe observable provider that tracked the timers.
expectedintThe expected active-timer count. Must be non-negative.
Returns
- AssertionResult
TUnit.Assertions.Core.AssertionResult.Passed when the active count equals
expected; otherwise Failed(string) naming the active timers' schedules.
Exceptions
- ArgumentNullException
valueis null.- ArgumentOutOfRangeException
expectedis negative.
HasActiveTimers(ObservableTimeProvider)
Asserts that at least one timer created through the ObservableTimeProvider is currently active. The positive-presence counterpart of HasNoActiveTimers(ObservableTimeProvider): useful for the registration half of a leak test ("the loop did start a timer") without pinning the exact count.
[GenerateAssertion(InlineMethodBody = false)]
public static AssertionResult HasActiveTimers(this ObservableTimeProvider value)
Parameters
valueObservableTimeProviderThe observable provider that tracked the timers.
Returns
- AssertionResult
TUnit.Assertions.Core.AssertionResult.Passed when at least one timer is active; otherwise Failed(string).
Exceptions
- ArgumentNullException
valueis null.
HasAtLeastActiveTimerCount(ObservableTimeProvider, int)
Asserts that at least count timers created through the
ObservableTimeProvider are currently active. Use it when a lower bound rather
than an exact count is the natural expectation (for example "the pool started at least its
minimum number of workers").
[GenerateAssertion(InlineMethodBody = false)]
public static AssertionResult HasAtLeastActiveTimerCount(this ObservableTimeProvider value, int count)
Parameters
valueObservableTimeProviderThe observable provider that tracked the timers.
countintThe minimum expected active-timer count. Must be non-negative.
Returns
- AssertionResult
TUnit.Assertions.Core.AssertionResult.Passed when the active count is at least
count; otherwise Failed(string) naming the active timers' schedules.
Exceptions
- ArgumentNullException
valueis null.- ArgumentOutOfRangeException
countis negative.
HasNextTimerDueApproximately(ObservableTimeProvider, TimeSpan, TimeSpan)
Asserts that the next pending timer's due time is within tolerance of
expected, inspecting the schedule the timer currently carries
without advancing the clock. The "next" timer is the one with the smallest due time
among the enabled (non-infinite) active timers. This verifies which delay a loop just
scheduled (for example a step of an exponential backoff) rather than advancing fake time and
inferring the delay from when the callback fires.
[GenerateAssertion(InlineMethodBody = false)]
public static AssertionResult HasNextTimerDueApproximately(this ObservableTimeProvider value, TimeSpan expected, TimeSpan tolerance)
Parameters
valueObservableTimeProviderThe observable provider that tracked the timers.
expectedTimeSpanThe expected due time of the next pending timer.
toleranceTimeSpanThe allowed absolute tolerance around
expected. Must be non-negative.
Returns
- AssertionResult
TUnit.Assertions.Core.AssertionResult.Passed when an enabled timer is pending and its due time is within
toleranceofexpected; otherwise Failed(string) with a message naming the expected and observed due times, or noting that no enabled timer was pending.
Exceptions
- ArgumentNullException
valueis null.- ArgumentOutOfRangeException
toleranceis negative.
HasNoActiveTimers(ObservableTimeProvider)
Asserts that no timers created through the ObservableTimeProvider remain undisposed: the canonical timer-leak check after a hosted service has stopped.
[GenerateAssertion(InlineMethodBody = false)]
public static AssertionResult HasNoActiveTimers(this ObservableTimeProvider value)
Parameters
valueObservableTimeProviderThe observable provider that tracked the timers.
Returns
- AssertionResult
TUnit.Assertions.Core.AssertionResult.Passed when no timer is active; otherwise Failed(string) with a message naming each surviving timer's schedule.
Exceptions
- ArgumentNullException
valueis null.
HasNoTimerFired(ObservableTimeProvider)
Asserts that no timer callback created through the ObservableTimeProvider has fired: the canonical check that a loop scheduled a timer but fake time was not advanced far enough to trigger it, or that a disposed timer never ran.
[GenerateAssertion(InlineMethodBody = false)]
public static AssertionResult HasNoTimerFired(this ObservableTimeProvider value)
Parameters
valueObservableTimeProviderThe observable provider that tracked the timers.
Returns
- AssertionResult
TUnit.Assertions.Core.AssertionResult.Passed when no callback has fired; otherwise Failed(string) with a message naming the cumulative fire count and the active timers' per-timer fire counts.
Exceptions
- ArgumentNullException
valueis null.
HasPendingTimerDueWithin(ObservableTimeProvider, TimeSpan, TimeSpan)
Asserts that the next pending timer's due time falls within the inclusive range
[min, max], inspecting the schedule the timer currently
carries without advancing the clock. The "next" timer is the one with the smallest
due time among the enabled (non-infinite) active timers.
[GenerateAssertion(InlineMethodBody = false)]
public static AssertionResult HasPendingTimerDueWithin(this ObservableTimeProvider value, TimeSpan min, TimeSpan max)
Parameters
valueObservableTimeProviderThe observable provider that tracked the timers.
minTimeSpanThe inclusive lower bound of the expected due-time range.
maxTimeSpanThe inclusive upper bound of the expected due-time range. Must be greater than or equal to
min.
Returns
- AssertionResult
TUnit.Assertions.Core.AssertionResult.Passed when an enabled timer is pending and its due time is within [
min,max]; otherwise Failed(string) with a message naming the range and observed due time, or noting that no enabled timer was pending.
Exceptions
- ArgumentNullException
valueis null.- ArgumentOutOfRangeException
maxis less thanmin.
HasTimerFiredAtLeast(ObservableTimeProvider, int)
Asserts that timer callbacks fired at least count times in total across
every timer created through the ObservableTimeProvider. The liveness lower-bound
counterpart of HasTimerFiredCount(ObservableTimeProvider, int): use it for a heartbeat where the exact number
of fires is subject to timing jitter but a minimum cadence must be proven.
[GenerateAssertion(InlineMethodBody = false)]
public static AssertionResult HasTimerFiredAtLeast(this ObservableTimeProvider value, int count)
Parameters
valueObservableTimeProviderThe observable provider that tracked the timers.
countintThe minimum cumulative fire count. Must be non-negative.
Returns
- AssertionResult
TUnit.Assertions.Core.AssertionResult.Passed when the cumulative fire count is at least
count; otherwise Failed(string) with a message naming the minimum and actual counts and the active timers' per-timer fire counts.
Exceptions
- ArgumentNullException
valueis null.- ArgumentOutOfRangeException
countis negative.
HasTimerFiredCount(ObservableTimeProvider, int)
Asserts that timer callbacks fired exactly expected times in total across
every timer created through the ObservableTimeProvider. The count is cumulative
and is not reset on disposal, so it reports how many times a hosted service's loop ran after a
fake-time advance, without reading the loop's side effects.
[GenerateAssertion(InlineMethodBody = false)]
public static AssertionResult HasTimerFiredCount(this ObservableTimeProvider value, int expected)
Parameters
valueObservableTimeProviderThe observable provider that tracked the timers.
expectedintThe expected cumulative fire count. Must be non-negative.
Returns
- AssertionResult
TUnit.Assertions.Core.AssertionResult.Passed when the cumulative fire count equals
expected; otherwise Failed(string) with a message naming the expected and actual counts and the active timers' per-timer fire counts.
Exceptions
- ArgumentNullException
valueis null.- ArgumentOutOfRangeException
expectedis negative.