Skip to content

Simulate network faults

Run the complete page example.

A service will not always reply quickly or successfully. Your tests can simulate a slow response, a broken connection or a server error to check how the app reacts.

NetworkBehavior adds those conditions to the test handler. You choose which failures to produce; your app's own code decides whether to retry, show an error or stop waiting.

Configure a predictable failure

1. Set delay and probabilities explicitly. The defaults include delay and a small failure chance. Set FailurePercent=0 when you only want HTTP errors. Probabilities are fractions: 0 means never and 1 means always.

2. Attach the behavior. Give it to new StubHttp(behavior) or assign http.Behavior. Assign null to disable simulation.

3. Send through a client and check the result. The runnable fault example checks a 503 response and then a thrown HttpRequestException with the configured message. Raw HttpClient receives the simulated exception directly. Refit can wrap a connection failure as ApiRequestException according to its error settings. See error handling.

Defaults and calculation methods

OverloadDescriptionParametersReturns
NetworkBehavior()Creates deterministic fault simulation with the standard seed and defaults.None.A behavior with random seed 0 and the defaults below.
NetworkBehavior(int seed)Creates fault simulation whose random sequence starts from your chosen seed.int seed: random sequence seed.A behavior with the supplied seed and the defaults below. The same ordered calls repeat within a runtime.
NextDelay()Draws the delay that the next simulated request would use.None.TimeSpan: next varied delay. The multiplier is clamped at zero.
NextIsFailure()Draws whether the next simulation produces a connection failure.None.bool: next trial against FailurePercent.
NextIsError()Draws whether the next simulation produces an HTTP error response.None.bool: next trial against ErrorPercent.
CreateFailure()Builds the configured connection exception without throwing it.None.Exception: result of FailureFactory(). Creates the exception without throwing it.
CreateErrorResponse()Builds a disposable HTTP error reply from the configured status code.None.HttpResponseMessage: fresh response with the configured status and an empty text body. The caller must dispose it.
PropertyTypeDefault and behavior
DelayTimeSpanTwo seconds. Base delay for simulation.
Variancedouble0.4. Fraction above and below the delay. Zero fixes the delay.
FailurePercentdouble0.03. Connection-failure probability.
ErrorPercentdouble0. HTTP-error probability when no connection failure occurs.
ErrorStatusCodeHttpStatusCodeInternalServerError (500). Injected response status.
FailureFactoryFunc<Exception>Creates an HttpRequestException with message Refit.Testing simulated network failure.
StubHttp.BehaviorNetworkBehavior, nullableConstructor-supplied behavior, or null to disable simulation. See handler construction.

Source: NetworkBehavior.cs and StubHttp.cs.

All properties are settable. Values are not validated. Use nonnegative delays, sensible variance and probabilities from zero through one. Random draws are locked, but concurrent call ordering can change which request gets each draw. Standalone calculation methods consume the same random sequence as handler simulation.

The handler first matches and consumes the route, then delays, then tries a connection failure, then tries an HTTP error. A connection failure prevents the error trial from running. Neither fault invokes your normal responder. Unmatched requests do not receive simulation. Retries need enough one-shot routes for each attempt, or a reusable route. Verification can succeed before the delay or failure finishes. Await the send separately.

Typed replies and AOT

For a standalone generated Refit test, register every request/reply model on a JsonSerializerContext with [JsonSerializable]. Set its context as TypeInfoResolver in the options passed to SystemTextJsonContentSerializer. Pass those settings to CreateGeneratedClient<T>. The complete setup applies even when a fault sometimes replaces the typed reply. Fault injection does not supply missing JSON metadata or validate native AOT compatibility.

Calculation and injection excerpts

The calculation excerpt exercises the seeded constructor and every standalone method. SimulationSeed is 7 and DefaultDelaySeconds is 2 in the complete source.

NetworkBehavior defaults = new();
NetworkBehavior behavior = new(SimulationSeed)
{
    Delay = TimeSpan.Zero,
    Variance = 0,
    FailurePercent = 0,
    ErrorPercent = 1,
    ErrorStatusCode = HttpStatusCode.ServiceUnavailable,
    FailureFactory = static () => new HttpRequestException(FailureMessage),
};
SampleCheck.Equal(TimeSpan.FromSeconds(DefaultDelaySeconds), defaults.Delay);
SampleCheck.Equal(TimeSpan.Zero, behavior.NextDelay());
SampleCheck.Equal(false, behavior.NextIsFailure());
SampleCheck.Equal(true, behavior.NextIsError());
SampleCheck.Equal(FailureMessage, behavior.CreateFailure().Message);
using HttpResponseMessage standalone = behavior.CreateErrorResponse();
SampleCheck.Equal(HttpStatusCode.ServiceUnavailable, standalone.StatusCode);

The injection excerpt checks both fault kinds through a raw client. Each attempt still needs a matching route.

using StubHttp http = new(behavior) { { Route.Get("/fault"), Reply.Text("normal") } };
using HttpClient client = CreateClient(http);
using HttpResponseMessage error = await client.GetAsync(new Uri("https://people.example/fault"));
SampleCheck.Equal(HttpStatusCode.ServiceUnavailable, error.StatusCode);
Verify(http);
behavior.FailurePercent = 1;
http.Add(Route.Get("/failure"), Reply.Text("normal"));
bool failed = false;
try
{
    using HttpResponseMessage response = await client.GetAsync(new Uri("https://people.example/failure"));
}
catch (HttpRequestException cause)
{
    failed = cause.Message == FailureMessage;
}

SampleCheck.Equal(true, failed);
http.Behavior = null;