Summary
Allow a mock to throw/simulate a network-level failure instead of returning a response, so tests can verify retry, circuit-breaker, and error-handling code.
Motivation
Resilient HTTP code must handle transport failures (HttpRequestException), timeouts (TaskCanceledException), and socket errors. There is no first-class way to simulate these today — and note that the current pipeline actually swallows responder exceptions: RequestMock.TrackRequest catches any exception and converts it into a 500 response, so a user cannot simulate a thrown transport error even via RespondsWith(_ => throw ...).
Proposed API (additive)
RequestMockResponseBuilder ThrowsException(Exception exception);
RequestMockResponseBuilder ThrowsException<TException>() where TException : Exception, new();
RequestMockResponseBuilder TimesOut(); // throws TaskCanceledException, mimicking HttpClient timeout
Usage:
mock.ForGet().WithPath("/flaky").ThrowsException<HttpRequestException>();
Implementation plan
dennisdoomen/mockly (core)
- Add an async response path (see shared dependency below) where an "exception responder" is awaited and the exception is propagated to the caller rather than caught.
- Carefully scope the existing
try/catch in TrackRequest (lines ~268–280): distinguish "intentionally simulated transport failure" (propagate) from "bug in a user responder" (current 500 behavior). Represent simulated failures as a dedicated responder type that the pipeline rethrows before/around the catch.
- Ensure the thrown request is still recorded in
HttpMock.Requests (as expected/handled) before throwing.
- Tests:
HttpRequestException surfaces to HttpClient caller; TimesOut() throws TaskCanceledException; the failed call is captured in Requests; Once()/Times(n) still apply.
dennisdoomen/fluentassertions.mockly (assertions — companion work)
CapturedRequestAssertions currently has BeExpected() / BeUnexpected(). Add:
BeASimulatedFailure(string because = "") // asserts CapturedRequest was a simulated throw, not a real response
This lets tests verify that the captured entry corresponds to an intended simulated failure rather than an unexpected 500.
- Both
v7/v8 packages must be updated via Shared/.
- Needs
AcceptApiChanges.ps1 in the FA.Mockly repo.
Shared dependency
Shares the async pipeline + CancellationToken work with the latency (#3) and async-responder (#7) issues. Recommend implementing that plumbing once.
Breaking-change analysis
- None for consumers at the API level (additive methods).
- Behavioral nuance: scoping the existing catch-all so simulated exceptions propagate is a deliberate change. Existing code relying on a thrown responder producing a
500 is unaffected (opt-in via new methods).
- Core:
api-approved workflow + AcceptApiChanges.ps1 in dennisdoomen/mockly.
- FA.Mockly:
AcceptApiChanges.ps1 in dennisdoomen/fluentassertions.mockly.
Summary
Allow a mock to throw/simulate a network-level failure instead of returning a response, so tests can verify retry, circuit-breaker, and error-handling code.
Motivation
Resilient HTTP code must handle transport failures (
HttpRequestException), timeouts (TaskCanceledException), and socket errors. There is no first-class way to simulate these today — and note that the current pipeline actually swallows responder exceptions:RequestMock.TrackRequestcatches any exception and converts it into a500response, so a user cannot simulate a thrown transport error even viaRespondsWith(_ => throw ...).Proposed API (additive)
Usage:
Implementation plan
dennisdoomen/mockly (core)
try/catchinTrackRequest(lines ~268–280): distinguish "intentionally simulated transport failure" (propagate) from "bug in a user responder" (current 500 behavior). Represent simulated failures as a dedicated responder type that the pipeline rethrows before/around the catch.HttpMock.Requests(as expected/handled) before throwing.HttpRequestExceptionsurfaces toHttpClientcaller;TimesOut()throwsTaskCanceledException; the failed call is captured inRequests;Once()/Times(n)still apply.dennisdoomen/fluentassertions.mockly (assertions — companion work)
CapturedRequestAssertionscurrently hasBeExpected()/BeUnexpected(). Add:v7/v8packages must be updated viaShared/.AcceptApiChanges.ps1in the FA.Mockly repo.Shared dependency
Shares the async pipeline + CancellationToken work with the latency (#3) and async-responder (#7) issues. Recommend implementing that plumbing once.
Breaking-change analysis
500is unaffected (opt-in via new methods).api-approvedworkflow +AcceptApiChanges.ps1indennisdoomen/mockly.AcceptApiChanges.ps1indennisdoomen/fluentassertions.mockly.