This episode covers testing WebSocket applications: server unit testing with Jest, client-server integration testing, load testing with Artillery and k6, and chaos testing to simulate network failures.

WebSocket bugs are hard to find manually: problems only appear with many connections, when a connection drops mid-stream, or when messages arrive out of order. Automated testing is the only way to ensure all those scenarios work before users find them.
Episode 26 covers testing WebSocket applications from unit to chaos: testing server logic in isolation, testing client-server interaction, flooding the server with load tests, and simulating failures to see how the application behaves.
Functions testable without a server are the best unit test targets — separate pure logic from side effects.
function bangunEnvelope(type, payload) {
return {
v: 2,
type,
id: "m_" + Date.now(),
data: payload,
};
}
module.exports = { bangunEnvelope };bangunEnvelope(type, payload) is pure: same input, same output, no IO. Functions like this test fast and give confidence in the part that changes most often — the message format.
To test handlers that depend on a WebSocket object, use a mock.
test("menolak pesan tanpa tipe", () => {
const mockWs = {
send: jest.fn(),
close: jest.fn(),
};
handlerPesan(mockWs, '{"teks":"halo"}');
expect(mockWs.close).toHaveBeenCalledWith(1008, "format tidak valid");
});mockWs replaces a real WebSocket object. The test asserts the behavior: close is called with code 1008 when a message has no type.
An integration test starts a real server on a test port, then connects a client.
const WebSocket = require("ws");
test("klien menerima broadcast", async () => {
const server = mulaiServer(0);
const klien1 = new WebSocket("ws://localhost:" + server.port);
const klien2 = new WebSocket("ws://localhost:" + server.port);
await tungguTerbuka(klien1);
await tungguTerbuka(klien2);
klien1.send(JSON.stringify({ type: "chat", teks: "halo" }));
const terima = await tungguPesan(klien2);
expect(JSON.parse(terima).teks).toBe("halo");
server.tutup();
});mulaiServer(0) uses a random port so tests do not collide. This test verifies an end-to-end scenario: one client sends, another client receives.
Also test the connection phases: handshake, messages, normal close, and abnormal close. Make sure the close, error, and ping handlers are called according to the scenario.
Artillery floods the server with connection scenarios.
config:
target: ws://localhost:8080
phases:
- duration: 60
arrivalRate: 10
scenarios:
- engine: ws
flow:
- send: '{"type":"chat","teks":"halo"}'
- think: 1arrivalRate: 10 opens 10 new connections per second for 60 seconds. The results show the maximum connections the server can hold before latency degrades.
k6 tests WebSocket with JavaScript scripts.
import ws from "k6/ws";
import { check } from "k6";
export default function () {
const res = ws.connect("ws://localhost:8080", (socket) => {
socket.on("open", () => {
socket.send('{"type":"chat","teks":"test"}');
});
socket.on("message", (data) => {
check(data, { "ada balasan": (d) => d.length > 0 });
});
});
}ws.connect(...) in k6 opens a connection, sends a message, and verifies the reply. Run it with k6 run script.js to see metrics like connection time and response percentiles.
Chaos testing deliberately breaks connections to see whether the system recovers.
test("reconnect memulihkan room", async () => {
const klien = bukaKoneksi();
klien.emit("room:join", "kritikal");
// paksa koneksi terputus
klien.disconnect();
klien.connect();
await tungguKoneksi(klien);
expect(klien.rooms).toContain("kritikal");
});klien.disconnect() then klien.connect() simulates a network drop. The test verifies the client returns to the room after a reconnect — exactly the behavior covered in episode 12.
Beyond dropped connections, also test:
Episode 26 equipped you with a safety net: unit tests for logic, integration tests for real interaction, load tests for capacity, and chaos tests for resilience.
Key takeaways:
In the next episode we cover Docker & containerization: a Dockerfile for the WebSocket server, multi-stage builds, docker networking, Docker Compose with Redis, and health checks.