Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Polly-Contrib/Simmy/llms.txt

Use this file to discover all available pages before exploring further.

A Polly resilience policy is only as good as the faults it has been tested against. Without a systematic way to inject failures, you are left hoping that your retry or fallback logic is correct — only finding out when a real dependency fails in production. Simmy closes that gap by letting you inject precisely the right fault, at the right injection rate, in ordinary unit and integration tests. The key insight is that an injection rate of 1.0 makes a chaos policy fully deterministic: it always fires. That predictability is what makes Simmy so useful in automated tests — you get a repeatable, reproducible failure scenario without any mocking infrastructure.

Unit Testing Pattern

In unit tests you want complete control. Set the injection rate to 1.0 so the fault fires on every execution, then wrap your resilience policy under test in a Policy.Wrap with the chaos policy innermost.
1

Build the chaos policy with 100 % injection rate

Always inject the fault so the test is deterministic. The Enabled() call with no argument defaults to true.
var fault = new HttpRequestException("Service unavailable");

var chaosPolicy = MonkeyPolicy.InjectException(with =>
    with.Fault(fault)
        .InjectionRate(1.0) // Always inject in unit tests
        .Enabled()
);
2

Wrap with the resilience policy under test

Place the chaos policy innermost so the outer resilience policy (retry, circuit breaker, fallback) reacts to the injected fault — exactly as it would react to a real dependency failure.
// myRetryPolicy = Policy.Handle<HttpRequestException>()
//                       .Retry(3);

var policyWrap = Policy.Wrap(myRetryPolicy, chaosPolicy);
3

Execute and assert

Run the wrapped execution and verify the resilience policy behaved as expected.
var callCount = 0;

// The retry policy should attempt the call 1 + 3 = 4 times before giving up
Assert.Throws<HttpRequestException>(() =>
    policyWrap.Execute(() =>
    {
        callCount++;
        return GetFromService(); // chaos will throw before this returns
    })
);

Assert.Equal(4, callCount); // initial attempt + 3 retries

Testing Individual Resilience Scenarios

Verify that your retry policy retries the correct number of times when Simmy injects the exception it handles.
var retryCount = 0;

var retryPolicy = Policy
    .Handle<SocketException>()
    .Retry(3, onRetry: (_, __) => retryCount++);

var chaosPolicy = MonkeyPolicy.InjectException(with =>
    with.Fault(new SocketException(errorCode: 10013))
        .InjectionRate(1.0)
        .Enabled()
);

var policyWrap = Policy.Wrap(retryPolicy, chaosPolicy);

Assert.Throws<SocketException>(() =>
    policyWrap.Execute(() => CallExternalService())
);

Assert.Equal(3, retryCount);

Async Test Pattern

For async code paths use the Async variants of all Simmy factory methods and pass a CancellationToken through.
var fault = new HttpRequestException("Async injected fault");

var chaosPolicy = MonkeyPolicy.InjectExceptionAsync(with =>
    with.Fault(fault)
        .InjectionRate(1.0)
        .Enabled()
);

var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .RetryAsync(3);

var policyWrap = Policy.WrapAsync(retryPolicy, chaosPolicy);

var callCount = 0;

await Assert.ThrowsAsync<HttpRequestException>(async () =>
    await policyWrap.ExecuteAsync(async ct =>
    {
        callCount++;
        return await httpClient.GetAsync("/api/resource", ct);
    }, CancellationToken.None)
);

Assert.Equal(4, callCount); // 1 initial + 3 retries

Integration Testing with Low Injection Rates

In integration or load tests you typically want to simulate realistic conditions rather than forcing every call to fail. Use a low injection rate (0.01–0.05) and run enough iterations that you can statistically verify your system degrades gracefully.
var chaosPolicy = MonkeyPolicy.InjectLatencyAsync(with =>
    with.Latency(TimeSpan.FromSeconds(3))
        .InjectionRate(0.05) // ~5 % of calls get extra latency
        .Enabled()
);

var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(2));

var policyWrap = Policy.WrapAsync(timeoutPolicy, chaosPolicy);

var timeouts = 0;

for (var i = 0; i < 200; i++)
{
    try
    {
        await policyWrap.ExecuteAsync(ct =>
            httpClient.GetAsync("/api/health", ct), CancellationToken.None);
    }
    catch (TimeoutRejectedException)
    {
        timeouts++;
    }
}

// With 5 % latency injection over 200 calls, expect roughly 10 timeouts
Assert.InRange(timeouts, 1, 30);
Low injection rates produce non-deterministic outcomes. Reserve them for integration or load tests where approximate ranges are acceptable assertions; use 1.0 whenever you need a hard guarantee in a unit test.

Frequently Asked Questions

Mocking replaces the dependency entirely, so your resilience policy never executes. Simmy keeps the real execution path intact and injects the fault inside the policy boundary, which means the retry, circuit breaker, or fallback you have configured actually runs and must handle the fault. This tests the policy wiring, not just the happy-path logic.
Yes. Tag the Execute call with a Context carrying an OperationKey, then use EnabledWhen to gate the fault on that key.
var chaosPolicy = MonkeyPolicy.InjectException(with =>
    with.Fault(new TimeoutException())
        .InjectionRate(1.0)
        .EnabledWhen((ctx, ct) => ctx.OperationKey == "GetOrder")
);

var context = new Context("GetOrder");
policyWrap.Execute(ctx => GetOrder(), context);
Use a short latency value — even TimeSpan.FromMilliseconds(100) — paired with a tight timeout policy. The goal is not to simulate the exact production latency value but to verify that your timeout policy fires when latency crosses its threshold.
var chaosPolicy = MonkeyPolicy.InjectLatencyAsync(with =>
    with.Latency(TimeSpan.FromMilliseconds(200))
        .InjectionRate(1.0)
        .Enabled()
);

// Timeout threshold is 100 ms — the 200 ms latency will always trip it
var timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(100));
var policyWrap = Policy.WrapAsync(timeoutPolicy, chaosPolicy);

await Assert.ThrowsAsync<TimeoutRejectedException>(async () =>
    await policyWrap.ExecuteAsync(ct => Task.Delay(0, ct), CancellationToken.None)
);
Yes — each MonkeyPolicy injects one category of fault (exception, result, latency, or behaviour). If you want to test the interaction of multiple fault types in the same scenario, build separate policies and add them both to your PolicyWrap. Order them innermost-first so each outer resilience policy sees the combined effects.
Yes. InjectBehaviour lets you run any arbitrary Action before the real call is placed. You can use it to corrupt shared state, write a poison entry to a cache, or close a connection — anything that would degrade the call in a realistic way.
var chaosPolicy = MonkeyPolicy.InjectBehaviour(with =>
    with.Behaviour(() => cache.Invalidate("orders"))
        .InjectionRate(0.10)
        .EnabledWhen((ctx, ct) => isEnabled(ctx, ct))
);

Build docs developers (and LLMs) love