DEV Community

Jeff
Jeff

Posted on Originally published at powerduck.com

Mocking SSE: Streaming Events and How a Client Handles a Dropped Connection

Quick answer: Mocking a Server-Sent Events endpoint is not the same as returning a list. The value under test is the stream over time: the framing of each event, the client's behavior mid-connection, and what it does when the connection drops and retries. A good SSE mock lets you control timing and injection of faults so you can verify those transitions instead of hoping they work in production.

I learned this on a job-progress UI. The backend emitted progress events while a long import ran, and the frontend updated a progress bar as events arrived. The first mock simply returned a JSON array of events. The component rendered perfectly in the test because it received everything at once. In production, the real stream stalled for nine seconds between two events, the browser dropped the idle connection, and the client's retry logic replayed the last event ID against a server that did not honor it. The progress bar jumped backward. None of that could show up in a mock that ignored time.

What makes SSE different from a normal response

SSE is a long-lived HTTP response with a specific text framing. Each event is a block of lines terminated by a blank line, and the connection stays open. The client keeps a built-in EventSource that reconnects automatically and sends the last event ID through the Last-Event-ID header.

The details that matter are the id: field that drives resume, the optional event: name, and the blank line that delimits a full event. A mock that returns the same bytes in one chunk does not exercise the client's incremental parsing.

What the mock has to let you control

I evaluate an SSE mock by the behaviors it lets me script rather than the raw bytes it produces. The contrast below explains why a static JSON fixture is not enough.

Behavior Static JSON fixture Time-aware SSE mock
Event delivery All events arrive immediately Each event arrives on a controlled schedule
Idle gaps Cannot be represented Inject delays to test keep-alive and spinners
Dropped connection Not modeled Close the socket mid-stream and observe retry
Resume Last-Event-ID never sent Reconnect and assert the stream resumes correctly
Error events Hard to insert Emit an error event and test recovery

Building a time-aware mock

A small Node mock using the web streams API gives full control over timing and lets you push events on a timer. The key is to keep the response open and write one framed event at a time.

With a mock like this, a test can schedule an event, advance or wait, assert the progress state, then deliberately close the controller to simulate a dropped connection and verify the client reconnects with the correct header.

Testing the reconnect and resume path

Automatic reconnect is a feature of EventSource, but resuming without duplicate or missed events is a contract your server must honor. On the mock, when a connection is reopened, read the Last-Event-ID header and resume after that point rather than restarting.

Assert two things explicitly: the client never reprocesses an event it already handled, and a gap in IDs does not cause it to hang silently. In our progress-bar bug, pinning this behavior in the test caught the replay issue immediately once the mock modeled a real disconnect.

Don't forget keep-alive and error framing

Proxies and load balancers close idle connections, so real servers send periodic comment lines (a line starting with :) as keep-alives. Add them to the mock on a long gap and confirm the client does not treat them as events. Also test an explicit error event followed by a successful reconnect, so the UI distinguishes a recoverable interruption from a terminal failure and shows the right state rather than spinning forever.

FAQ

Can I just return the full event list as a JSON fixture?

Only for tests that check static rendering. It cannot verify timing, keep-alive, incremental parsing, or reconnect behavior, which are the parts that actually fail in a streaming UI. Use a time-aware mock for anything involving the live connection.

What does the client send when it reconnects?

The browser's EventSource automatically includes the last event ID in the Last-Event-ID header. The server should resume after that ID so the client does not reprocess or miss events.

How do I simulate a dropped connection in a test?

Close the underlying stream or socket after delivering some events, then observe the client reconnect and assert the request carries the correct Last-Event-ID and that the stream resumes without duplicates.

Why does my connection die during long gaps?

An intermediary likely closed an idle connection. Servers send SSE comment lines as periodic keep-alives; model those in the mock and confirm the client ignores them while the connection stays open.

This is part of the API Mocking Guide — start there for the broader patterns that keep mocks faithful to the real contract.

Top comments (0)