Table of Contents

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

value ObservableTimeProvider

The observable provider that tracked the timers.

expected int

The 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

value is null.

ArgumentOutOfRangeException

expected is 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

value ObservableTimeProvider

The 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

value is 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

value ObservableTimeProvider

The observable provider that tracked the timers.

count int

The 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

value is null.

ArgumentOutOfRangeException

count is 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

value ObservableTimeProvider

The observable provider that tracked the timers.

expected TimeSpan

The expected due time of the next pending timer.

tolerance TimeSpan

The 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 tolerance of expected; otherwise Failed(string) with a message naming the expected and observed due times, or noting that no enabled timer was pending.

Exceptions

ArgumentNullException

value is null.

ArgumentOutOfRangeException

tolerance is 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

value ObservableTimeProvider

The 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

value is 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

value ObservableTimeProvider

The 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

value is 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

value ObservableTimeProvider

The observable provider that tracked the timers.

min TimeSpan

The inclusive lower bound of the expected due-time range.

max TimeSpan

The 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

value is null.

ArgumentOutOfRangeException

max is less than min.

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

value ObservableTimeProvider

The observable provider that tracked the timers.

count int

The 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

value is null.

ArgumentOutOfRangeException

count is 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

value ObservableTimeProvider

The observable provider that tracked the timers.

expected int

The 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

value is null.

ArgumentOutOfRangeException

expected is negative.