Circuit Breaker Testing

Circuit breaker testing verifies that a service correctly transitions among closed, open, and half-open states when a dependency fails. Tests should inject controlled failures and verify both state transitions and the resulting request behavior.

A circuit breaker protects an application from repeatedly calling an unhealthy dependency. In the closed state, calls normally reach the dependency; after configured failure conditions are met, the breaker opens and subsequent calls are rejected or handled through a fallback without contacting the dependency.

Failure injection is the core testing technique. A Java test can replace or stub the downstream client so that calls produce exceptions, timeouts, or other configured failures. The test then checks that the breaker records those failures and eventually changes state according to its configuration.

The half-open state is important because it provides a controlled recovery probe. After the open-state wait period, the breaker permits a limited number of trial calls. A successful trial can allow the breaker to close, while another qualifying failure can return it to the open state.

Good tests verify observable behavior rather than relying only on internal implementation details. They should establish the initial state, inject the required failure pattern, assert whether downstream calls are permitted, advance or control time when necessary, and verify recovery behavior.

Key points

Interview questions

How would you test that a circuit breaker opens after dependency failures?

Stub the downstream dependency to throw the failure type that the breaker is configured to count. Send enough requests to satisfy the configured failure condition, then verify that the breaker changes from closed to open. Also verify that a request made while open does not invoke the downstream dependency. The test should use deterministic configuration and controlled failure injection rather than depending on an actual outage.

How do you test recovery from an open circuit breaker?

First force the breaker into the open state using controlled failures. Then advance the breaker’s clock or otherwise wait for its configured open interval without introducing an uncontrolled test delay. Verify that the next permitted request enters the half-open behavior. Make the trial request succeed and verify that subsequent normal traffic is allowed again.

Why is failure injection preferable to testing against an actually unavailable service?

Injected failures make the test deterministic and repeatable. The test can produce the exact exception, timeout, or failure sequence needed to exercise the breaker. It also avoids network dependencies, long delays, and environmental failures. This keeps the test focused on the application’s circuit-breaking behavior rather than infrastructure availability.

What should you verify while the circuit is open?

The primary assertion is that protected calls are rejected or handled without invoking the dependency. The test should also verify the expected fallback or error behavior exposed to the caller. If the library exposes reliable state information, the test can verify that the breaker remains open until its recovery condition is reached. This distinguishes a functioning circuit breaker from code that merely catches exceptions.

What edge cases would you include in circuit breaker tests?

I would cover the configured trip boundary, repeated failures, successful recovery, and failure during the half-open probe. I would also test the distinction between counted and ignored exceptions, because not every downstream error necessarily represents a breaker-worthy failure. If the implementation supports concurrent requests, I would examine behavior when multiple calls arrive during transitions. Finally, I would verify timing behavior using controllable time where the implementation permits it.