Class WillLogAssertion
- Namespace
- LogAssertions.TUnit
- Assembly
- LogAssertions.TUnit.dll
TUnit assertion that waits until a Microsoft.Extensions.Logging.Testing.FakeLogCollector holds matching log records: the event-driven
form of HasLogged() for records that background work logs after the call that started it returns. Entry
point: Assert.That(collector).WillLog(timeout, cancellationToken), then the same filters as
HasLogged() and a count terminator.
[AssertionExtension("WillLog")]
public sealed class WillLogAssertion : LogAssertionBase<WillLogAssertion>, IAssertion
- Inheritance
-
Assertion<FakeLogCollector>WillLogAssertion
- Implements
-
IAssertion
- Inherited Members
-
Assertion<FakeLogCollector>.AssertAsync()Assertion<FakeLogCollector>.GetAwaiter()Assertion<FakeLogCollector>.AndAssertion<FakeLogCollector>.Or
Examples
await Assert.That(collector).WillLog(TimeSpan.FromSeconds(5), cancellationToken)
.AtLevel(LogLevel.Warning).Containing("would collide", StringComparison.Ordinal).Once();
Remarks
It waits on the collector's stream of records, the one
WaitForLogAsync(FakeLogCollector, ILogRecordFilter, CancellationToken)
reads. The count is read from the collector right after the minimum number of matching records has been captured.
Records captured by then count, including ones logged before the assertion ran. A record that arrives after the
assertion has passed isn't checked: to pin "no more than one", assert HasLogged()...Once() after the work
has settled. The assertion fails when that count is above the maximum. When the timeout or the token ends
the wait, the records the collector holds at that moment decide: enough matching records pass (also a record
captured just before the timeout fired, or records already captured when the token was canceled before the
assertion ran); otherwise it fails with the expectation, the count, and the captured records, the same diagnostics
as HasLogged(), not a bare cancellation. A chain whose levels all sit below the collector's capture floor
fails at once, since such a record can never be captured.
Clock: the timeout runs in real time (System), whatever clock the collector uses,
and starts when the assertion runs (at await), not when the chain is built. It bounds how long the
test waits for real background work. When that work waits on a fake clock, start the assertion first, advance the
clock, then await the assertion: put the chain in a local function that awaits it (TUnit's analyzer requires every
Assert.That to be awaited), call it, advance, and await the task it returned. That
await is what surfaces the failure message: a helper that advances until the task completes
surfaces it only if the helper awaits the task too.
A wait proves that records arrived, not that no more follow, so there is no Never() or AtMost(n): to
pin "no more than one", let the work finish and assert HasLogged()...Once(); for absence, use
HasNotLogged() after the work has finished. The wait is built on
WaitForLogsAsync(FakeLogCollector, ILogRecordFilter, int, CancellationToken): no polling and no real-time delay; the timeout is a
bound, not a poll interval.
Constructors
WillLogAssertion(AssertionContext<FakeLogCollector>, TimeSpan, CancellationToken)
Initializes a waiting log assertion bounded by timeout (real time) and
cancellationToken. Called by the TUnit source generator.
public WillLogAssertion(AssertionContext<FakeLogCollector> context, TimeSpan timeout, CancellationToken cancellationToken = default)
Parameters
contextAssertionContext<FakeLogCollector>The assertion context supplied by TUnit.
timeoutTimeSpanThe longest the wait may take, in real time, counted from when the assertion runs. Must be positive and at most about 49 days. Choose it below the test's own timeout, so the wait fails with its diagnostics first.
cancellationTokenCancellationTokenEnds the wait early, for example the test's token.
Remarks
The count is read from the collector right after the minimum number of matching records has been captured.
Records captured by then count, including ones logged before the assertion ran. A record that arrives after the
assertion has passed isn't checked: to pin "no more than one", assert HasLogged()...Once() after the work
has settled.
Clock: timeout runs on System, from the moment the assertion runs.
Exceptions
- ArgumentOutOfRangeException
timeoutis zero, negative (including InfiniteTimeSpan), or longer than a CancellationTokenSource accepts.
Methods
AtLeast(int)
Waits for at least count matching records. Without a count terminator the chain
waits for at least one.
public WillLogAssertion AtLeast(int count)
Parameters
countintThe minimum. Must be positive.
Returns
- WillLogAssertion
This assertion for chaining.
Exceptions
- ArgumentOutOfRangeException
countis not positive.
Between(int, int)
Waits until the count of matching records is in the inclusive range [min,
max], and fails at once if the wait first sees more than max.
public WillLogAssertion Between(int min, int max)
Parameters
minintThe minimum (inclusive). Must be positive.
maxintThe maximum (inclusive). Must be at least
min.
Returns
- WillLogAssertion
This assertion for chaining.
Exceptions
- ArgumentOutOfRangeException
minis not positive, ormaxis less thanmin.
CheckAsync(EvaluationMetadata<FakeLogCollector>)
Implements the specific check logic for this assertion. Called after the context has been evaluated. Override this method if your assertion uses the default AssertAsync() flow. If you override AssertAsync() with custom logic (like AndAssertion/OrAssertion), you don't need to implement this.
protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<FakeLogCollector> metadata)
Parameters
metadataEvaluationMetadata<FakeLogCollector>Metadata about the evaluation including value, exception, and timing information
Returns
- Task<AssertionResult>
The result of the assertion check
Exactly(int)
Waits for exactly count matching records: passes when the count reaches it, and
fails at once if the wait first sees more.
public WillLogAssertion Exactly(int count)
Parameters
countintThe count. Must be positive.
Returns
- WillLogAssertion
This assertion for chaining.
Exceptions
- ArgumentOutOfRangeException
countis not positive.
GetExpectation()
Gets a human-readable description of what this assertion expects. Used in error messages.
protected override string GetExpectation()
Returns
GetMatch()
Runs the assertion and returns the single matched record once the wait passes, as
HasLogged().GetMatch() does. Requires the count to be exactly one (Once(), Exactly(1),
or Between(1, 1)).
public Task<FakeLogRecord> GetMatch()
Returns
- Task<FakeLogRecord>
The single matched record.
Exceptions
- InvalidOperationException
The chain's count expectation is not exactly one; or the assertion failed inside
Assert.Multiple(), which records the failure instead of throwing it, so there is no match to return (the message names the failure, and the scope still reports it).
GetMatches()
Runs the assertion and returns the matched records once the wait passes, as
HasLogged().GetMatches() does.
public Task<IReadOnlyList<FakeLogRecord>> GetMatches()
Returns
- Task<IReadOnlyList<FakeLogRecord>>
The records that matched when the wait passed, in the order they were logged; a snapshot.
Exceptions
- InvalidOperationException
The assertion failed inside
Assert.Multiple(), which records the failure instead of throwing it, so there are no matches to return; the message names the failure, and the scope still reports it.
Once()
Waits for exactly one matching record: passes when the first arrives, and fails at once if the wait first sees two.
public WillLogAssertion Once()
Returns
- WillLogAssertion
This assertion for chaining.