Learn WebSocket - Testing WebSocket Applications
Episode 26 of 34

Learn WebSocket - Testing WebSocket Applications

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

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.

Unit Testing

Testing Pure Logic

Functions testable without a server are the best unit test targets — separate pure logic from side effects.

JSSeparating logic for testing
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.

Mocking Connections

To test handlers that depend on a WebSocket object, use a mock.

JSTesting a handler with 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.

Integration Testing

Testing Client and Server Together

An integration test starts a real server on a test port, then connects a client.

JSIntegration test with a real 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.

Testing the Connection Lifecycle

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.

Load Testing

Artillery for WebSocket

Artillery floods the server with connection scenarios.

Artillery load test script
config:
  target: ws://localhost:8080
  phases:
    - duration: 60
      arrivalRate: 10
scenarios:
  - engine: ws
    flow:
      - send: '{"type":"chat","teks":"halo"}'
      - think: 1

arrivalRate: 10 opens 10 new connections per second for 60 seconds. The results show the maximum connections the server can hold before latency degrades.

k6 with the WebSocket Extension

k6 tests WebSocket with JavaScript scripts.

JSk6 WebSocket script
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

Simulating Failures

Chaos testing deliberately breaks connections to see whether the system recovers.

JSSimulating a dropped connection
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.

Other Chaos Scenarios

Beyond dropped connections, also test:

  • High latency: delay messages to see timeouts and retries.
  • Slow network: reduce bandwidth to test backpressure.
  • Server crash: kill the server and make sure the client retries.
  • Out-of-order: send messages out of order to test sequence numbers.

Closing

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:

  • Separate pure logic from IO so it is easy to test.
  • Mock WebSocket objects for unit-testing handlers.
  • Integration tests use a real server on a random port.
  • Artillery and k6 measure capacity with real scenarios.
  • Chaos testing simulates dropped connections, latency, and crashes.
  • Test the full lifecycle: handshake, messages, and closure.

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.

Learn WebSocket - Testing WebSocket Applications | Learn WebSocket