In this lesson, you will learn how to verify that your Polly retry and circuit breaker strategies actually intercept failures and protect your system as intended.
When we implement resilience policies, we often assume they work, but "silent" policy misconfigurations can lead to cascading failures. To test these, we must move beyond unit testing logic and into deterministic failure injection. We need to force our dependencies (like an HttpClient) to fail on cue so we can observe how our policies respond.
Verifying Retries
To test a retry policy, you don't want to wait for actual network timeouts. Instead, use a DelegatingHandler or a library like Moq to force the first N requests to return a 503 Service Unavailable, then return a 200 OK.
int callCount = 0;
var handlerMock = new Mock<HttpMessageHandler>();
handlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(() => {
callCount++;
return callCount < 3
? new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)
: new HttpResponseMessage(HttpStatusCode.OK);
});
By asserting that callCount equals 3, you confirm that your retry policy intercepted the failures and attempted the operation exactly as configured.
Verifying the Circuit Breaker
The circuit breaker is trickier because it relies on state. Testing this requires two phases:
- The Trip: Trigger the failure threshold (e.g., three consecutive errors) and assert that the subsequent call throws
BrokenCircuitExceptionimmediately, without even hitting the underlying mock. - The Recovery: Use a
Task.Delayor a time-manipulation library to wait for thebreakDuration, then verify that the next request succeeds and resets the circuit.
Don't use real System.Threading.Thread.Sleep in these tests. Instead, inject an ITimeProvider (available in .NET 8+) into your policies so your tests can "fast-forward" time, allowing your circuit breaker tests to complete in milliseconds while simulating long waiting periods.
Key Takeaway: Never test resilience by waiting for real-world failures; use mocked handlers to inject deterministic errors and verify the policy's state transitions.