Skip to content

Add simulated response latency .After(TimeSpan) (closes #117) - #134

Draft
dennisdoomen wants to merge 2 commits into
mainfrom
dennisdoomen/response-latency-after
Draft

Add simulated response latency .After(TimeSpan) (closes #117)#134
dennisdoomen wants to merge 2 commits into
mainfrom
dennisdoomen/response-latency-after

Conversation

@dennisdoomen

Copy link
Copy Markdown
Owner

Closes #117

Stacked PR: This builds on #132 (the async-responder pipeline from #120) and targets dennisdoomen/async-responder-pipeline, not main. Review/merge #132 first.

Summary

Adds a way to simulate a slow endpoint so tests can exercise timeout, cancellation, and resilience (e.g. Polly) behavior.

After(TimeSpan delay) stores the delay on the RequestMock and the async response path awaits Task.Delay(delay, cancellationToken) before invoking the responder, reusing the CancellationToken already threaded from MockHttpMessageHandler.SendAsync through HttpMock.HandleRequest into RequestMock.TrackRequestAsync by the stacked async-responder work.

A cancelled token throws TaskCanceledException/OperationCanceledException, exactly as a real HttpClient would (e.g. when HttpClient.Timeout is shorter than the delay, or when the caller's token is cancelled).

mock.ForGet().WithPath("/slow")
    .RespondsWithStatus(HttpStatusCode.OK)
    .After(TimeSpan.FromSeconds(2));

New public API

  • RequestMockResponseBuilder RequestMockResponseBuilder.After(TimeSpan delay)

The change is purely additive and backward-compatible. A negative delay throws ArgumentOutOfRangeException.

Tests

New WhenSimulatingResponseLatency specs (xUnit + FluentAssertions, AAA):

  • response is delayed by the configured amount
  • HttpClient.Timeout shorter than the delay throws TaskCanceledException
  • an externally cancelled token cancels the wait
  • a negative delay is rejected

Verified green on both net472 and net8.0. Updated the public-API approval files (net472.verified.txt, net8.0.verified.txt) via AcceptApiChanges.ps1, and added docs to website/docs/advanced.md, intro.md, README.md, and SKILL.md.

dennisdoomen and others added 2 commits May 30, 2026 09:48
Introduce an internal async responder path on RequestMock and converge the synchronous Responder onto a single async execution path (TrackRequestAsync). Thread the CancellationToken from MockHttpMessageHandler.SendAsync through HttpMock.HandleRequest into RequestMock. Add public RespondsWith overloads accepting Func<RequestInfo, Task<HttpResponseMessage>> and Func<RequestInfo, CancellationToken, Task<HttpResponseMessage>>. The existing synchronous Responder property and TrackRequest method are preserved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add an After(TimeSpan) method on RequestMockResponseBuilder that stores a delay on the RequestMock and awaits Task.Delay before invoking the responder on the async response path, honoring the CancellationToken flowing from SendAsync. A cancelled token (e.g. via HttpClient.Timeout or an external token) throws TaskCanceledException/OperationCanceledException as a real HttpClient would.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dennisdoomen dennisdoomen added the enhancement New feature or request label May 30, 2026
}

[Fact]
public async Task A_negative_delay_is_rejected()
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));

// Act
Func<Task> act = () => client.GetAsync("https://localhost/slow", cts.Token);
/// </remarks>
/// <param name="delay">The amount of time to wait before producing the response.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="delay"/> is negative.</exception>
public RequestMockResponseBuilder After(TimeSpan delay)
@github-actions

github-actions Bot commented May 30, 2026

Copy link
Copy Markdown

Test Results

  3 files  ±0    3 suites  ±0   8s ⏱️ +3s
101 tests +4  101 ✅ +4  0 💤 ±0  0 ❌ ±0 
199 runs  +8  199 ✅ +8  0 💤 ±0  0 ❌ ±0 

Results for commit a2b689e. ± Comparison against base commit e3fc9d6.

♻️ This comment has been updated with latest results.

@dennisdoomen
dennisdoomen force-pushed the dennisdoomen/async-responder-pipeline branch from e3fc9d6 to 7df3f66 Compare June 20, 2026 14:06
Base automatically changed from dennisdoomen/async-responder-pipeline to main June 20, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants