WebSockets Intro: Real-Time APIs and How to Test Them
WebSockets are persistent, full-duplex, and weirdly under-tested. Here's a practical guide.
What WebSockets are
WebSocket is a protocol (RFC 6455) that upgrades a regular HTTP connection into a persistent, full-duplex channel where either side can send messages at any time.
REST is a request-response model. A client asks, the server answers, the connection ends. WebSockets flip that: the connection opens once and stays open, with messages flowing in both directions until either side hangs up.
The handshake
Every WebSocket connection starts as an HTTP request with special headers:
GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
The server responds:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Status 101 = protocol switched. The TCP connection now speaks WebSocket frames, not HTTP. The URL usually uses ws:// or wss:// (TLS) instead of http:///https://.
What flows over the pipe
Messages. Text or binary. In either direction. As often as you like:
← { "type": "subscribe", "channel": "orders.user-123" }
→ { "type": "order.created", "id": "order-abc" }
→ { "type": "order.paid", "id": "order-abc" }
← { "type": "ping" }
→ { "type": "pong" }
The format is up to you. Most APIs use JSON; some use MessagePack or Protocol Buffers for performance.
Where WebSockets shine
- Chat and messaging — Slack, Discord, WhatsApp Web.
- Collaborative editing — Google Docs, Figma, Notion.
- Live dashboards and trading — where every millisecond matters.
- Gaming — state sync, matchmaking.
- GraphQL subscriptions — the standard transport for live GraphQL.
- Presence — "who's online right now".
The common thread: the server has information to push to the client without waiting to be asked.
WebSockets vs Server-Sent Events vs long polling
| WebSocket | SSE | Long polling | |
|---|---|---|---|
| Direction | Both ways | Server → client only | Client → server (poll) |
| Protocol | Custom frames over TCP | HTTP streaming | Repeated HTTP |
| Reconnect logic | DIY | Built-in | DIY |
| Browser support | Excellent | Good | Universal |
| Binary data | Yes | No (text only) | Yes |
| Typical use | Chat, games, GraphQL subs | Live notifications, stock tickers | Fallback for old environments |
If you only need server → client push (notifications, live tickers), SSE is simpler. If you need both directions, WebSockets are the tool.
What testers need to understand
Testing WebSockets is fundamentally different from testing REST:
- Connections are stateful. A test has to establish, exchange, and tear down. No stateless "one request, one assertion."
- Ordering matters. The server might send messages in an order your client doesn't expect.
- Timing matters. A test might need to wait for a server push that hasn't arrived yet.
- Multiple clients matter. Chat, presence, collaborative editing — you can't test with one client.
- Reconnects matter. WebSockets drop. Your tests must cover reconnect, resume, and missed-message recovery.
A testable WebSocket scenario
A minimal chat scenario:
- Client A connects, authenticates.
- Client B connects, authenticates.
- Client A subscribes to
room:lobby. - Client B subscribes to
room:lobby. - Client A sends
{"type":"message","text":"hi"}. - Client B receives
{"type":"message","from":"A","text":"hi"}within 500ms. - Client B disconnects.
- Client A sends another message — should succeed with no errors.
- Client A disconnects.
Each step is an assertion point. A good WebSocket test runner (like ShiftLeft's scenario runner) orchestrates multiple clients with timing guarantees.
Core assertions
For every WebSocket feature:
- Connection — handshake succeeds, correct subprotocol selected.
- Auth — without token → close with 4401; with bad token → close with 4403; with good token → stays open.
- Subscription — after subscribe, relevant events flow; after unsubscribe, they stop.
- Message shape — every pushed message validates against a schema (JSON Schema or Protobuf).
- Ordering — if the API guarantees order, assert it; if not, don't.
- Timing — pushes arrive within an SLA (e.g., 200ms p95).
- Heartbeats — idle connections stay open via ping/pong; test that missing heartbeats trigger close.
- Reconnect — after drop, resume or re-sync works as specified.
- Backpressure — if the client is slow, does the server buffer, drop, or close?
Common bugs WebSocket tests catch
1. Broadcasting to wrong room. Client A subscribes to room:lobby, but messages from room:general leak through. Easy to miss until real users see each other's chats.
2. Unauthorized subscriptions. A client subscribes to user:123:private and starts receiving messages it shouldn't. Server didn't check ownership.
3. Memory leaks on disconnect. Server doesn't clean up subscriptions, and after 10k connect/disconnect cycles the process eats 8 GB.
4. Silent drops. Server closes the socket but emits no close frame. Client hangs indefinitely.
5. Reconnect storms. One server restart, all clients reconnect in the same second, server falls over. Tests should verify staggered reconnect / jittered backoff.
Tools for WebSocket testing
- wscat — interactive CLI, good for exploration.
- websocat — scripted CLI, good for quick automated checks.
- Postman — has WebSocket support, good for manual testing.
- k6 — load testing with WebSocket support.
- ShiftLeft — scenario-based multi-client orchestration with schema validation and timing SLA assertions.
What's next
You've now seen REST, GraphQL, SOAP, and WebSockets. The final protocol lesson is a side-by-side: REST vs GraphQL vs SOAP — when to pick what, and how testing differs across them.
Related lessons
REST is the default API style on the web. Here's what it actually means — stripped of jargon and with runnable examples.
GraphQL lets the client decide what data to fetch. Here's how it works and when it beats REST.
The same operation in three protocols. Pick the right one for the job — and test it right.