Testing Error Boundaries and Suspense
Learn to test Suspense and Error Boundaries by validating committed fallback, error, and recovery phases rather than implementation details, even when rendering is time-based and concurrent.
As React applications scale, rendering is no longer a single uninterrupted process. Components may suspend while waiting for data. Nested regions may stream in progressively. Errors may be thrown during rendering and caught by boundaries designed to isolate failure. React 19 formalizes this model: rendering is scheduled, interruptible, and coordinated across boundaries.
In small demos, loading and error states are often tested with simplistic assumptions: wait 500ms, expect a spinner; mock an error, expect a message. But production Suspense flows are more complex. Fallbacks appear and disappear. Errors may surface in nested regions while the outer shell remains stable. Retry flows must reset boundaries without tearing down unrelated UI. Time-based behavior, such as a delayed fallback to avoid flicker, adds even more nuance.
The actual problem we must solve is this:
How do we test Suspense and Error Boundaries in a way that validates the user-visible contract without tying tests to timing hacks or internal mechanics?
Suspense and Error Boundaries define rendering seams. A Suspense boundary commits a fallback when a child suspends. An Error Boundary commits an alternative UI when a descendant throws. Both introduce explicit phases into rendering. Our tests must assert against those committed phases:
What renders before suspension?
When does fallback appear?
What persists during failure?
What resets on retry?
What changes when time thresholds are involved?