Skip to content

Add simulated network failures ThrowsException/TimesOut - #136

Merged
dennisdoomen merged 1 commit into
mainfrom
dennisdoomen/simulated-network-failures
Aug 10, 2026
Merged

Add simulated network failures ThrowsException/TimesOut#136
dennisdoomen merged 1 commit into
mainfrom
dennisdoomen/simulated-network-failures

Conversation

@dennisdoomen

Copy link
Copy Markdown
Owner

Closes #116

Stacking

This is the top of a 3-PR stack and targets dennisdoomen/response-latency-after:

It builds on the async responder path (TrackRequestAsync, CancellationToken flowing from SendAsyncHandleRequestRequestMock) introduced in those PRs and does not re-implement them.

Summary

Adds first-class support for simulating network-level failures so tests can exercise retry, circuit-breaker and error-handling code.

Previously the pipeline swallowed every responder exception and turned it into a 500 response, so there was no way to simulate a thrown transport error. This change introduces a dedicated simulated-failure responder that the async pipeline propagates to the HttpClient caller instead of catching:

  • A new internal SimulatedFailureResponder type represents an intentionally simulated failure.
  • RequestMock.TrackRequestAsync records the request (incrementing the invocation count and adding it to any request collection) and returns the captured request carrying the exception, rather than throwing inline. This guarantees the failed call is recorded in HttpMock.Requests (as expected/handled) before the exception reaches the caller.
  • HttpMock.HandleRequest rethrows the captured exception via ExceptionDispatchInfo after recording it.
  • The existing try/catch that converts responder exceptions into a 500 is carefully scoped so genuine bugs in user RespondsWith responders keep the current 500 behavior, while intentionally-simulated failures propagate.
  • Once() / Times(n) and After() continue to apply to simulated failures.

New public API

RequestMockResponseBuilder ThrowsException(Exception exception);
RequestMockResponseBuilder ThrowsException<TException>() where TException : Exception, new();
RequestMockResponseBuilder TimesOut(); // throws TaskCanceledException, mimicking an HttpClient timeout

Plus CapturedRequest.SimulatedFailure (Exception?) so a captured entry can be inspected to tell a simulated throw apart from an unexpected 500.

Usage:

mock.ForGet().WithPath("/flaky").ThrowsException<HttpRequestException>();
mock.ForGet().WithPath("/slow").TimesOut();

This is additive and backward-compatible. The API verification baselines (net8.0, net472) were regenerated via AcceptApiChanges.ps1.

Tests

New WhenSimulatingNetworkFailures spec class (xUnit + FluentAssertions, AAA) covering:

  • HttpRequestException surfaces to the HttpClient caller (generic and specific-instance overloads)
  • TimesOut() throws TaskCanceledException
  • the failed call is captured in HttpMock.Requests and in a CollectingRequestsIn collection
  • Once() / Times(n) still apply
  • a genuinely buggy responder still results in a 500
  • null exception is rejected

All 108 specs pass on both net8.0 and net472; Release build (warnings-as-errors, all analyzers) is clean.

Follow-up

Issue #116 also describes a companion CapturedRequestAssertions.BeASimulatedFailure() assertion in the separate dennisdoomen/fluentassertions.mockly repo (v7/v8 via Shared/). That is not part of this repo/workspace and needs its own PR; the new public CapturedRequest.SimulatedFailure property is exposed here so that assertion can be built on top of it.

@dennisdoomen dennisdoomen added the enhancement New feature or request label May 30, 2026
Comment thread Mockly.Specs/HttpMockSpecs.cs Fixed
Comment thread Mockly/RequestMockBuilder.cs Fixed
Comment thread Mockly/RequestMockBuilder.cs Fixed
{
private readonly Func<Exception> exceptionFactory;

public SimulatedFailureResponder(Func<Exception> exceptionFactory)
@github-actions

github-actions Bot commented May 30, 2026

Copy link
Copy Markdown

Test Results

  3 files  ± 0    3 suites  ±0   20s ⏱️ +12s
212 tests + 9  212 ✅ + 9  0 💤 ±0  0 ❌ ±0 
421 runs  +18  421 ✅ +18  0 💤 ±0  0 ❌ ±0 

Results for commit fb6e07a. ± Comparison against base commit 5c41aba.

♻️ This comment has been updated with latest results.

@coveralls

coveralls commented May 30, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 31386873526

Coverage increased (+0.4%) to 85.461%

Details

  • Coverage increased (+0.4%) from the base build.
  • Patch coverage: 50 of 50 lines across 5 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1573
Covered Lines: 1413
Line Coverage: 89.83%
Relevant Branches: 511
Covered Branches: 368
Branch Coverage: 72.02%
Branches in Coverage %: Yes
Coverage Strength: 337.93 hits per line

💛 - Coveralls

@dennisdoomen
dennisdoomen marked this pull request as ready for review August 10, 2026 11:21
Base automatically changed from dennisdoomen/response-latency-after to main August 10, 2026 11:31
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dennisdoomen
dennisdoomen force-pushed the dennisdoomen/simulated-network-failures branch from 2ca9d81 to fb6e07a Compare August 10, 2026 12:11
@dennisdoomen dennisdoomen changed the title Add simulated network failures ThrowsException/TimesOut (closes #116) Add simulated network failures ThrowsException/TimesOut Aug 10, 2026
}

[Fact]
public async Task A_null_exception_is_rejected()
@dennisdoomen
dennisdoomen merged commit cffaf6f into main Aug 10, 2026
10 checks passed
@dennisdoomen
dennisdoomen deleted the dennisdoomen/simulated-network-failures branch August 10, 2026 12:22
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.

Add simulated network failures (ThrowsException / TimesOut) for resilience testing

3 participants