Built-in ToolsUnit Testing

Unit Testing Your Server

The @colyseus/testing package provides utility methods for testing your Colyseus application. It simulates client connections and message exchange using the JavaScript SDK, so you can validate both server and client behavior in a single test.

Use it with your favorite testing framework, such as Vitest, Mocha, or Jest.

Installation

This package is installed by default on new projects created via npm create colyseus-app.

npm install --save-dev @colyseus/testing

Getting Started

Boot the test server with your app.config.ts, then create rooms and connect simulated clients inside each test case.

App.test.ts
import { ColyseusTestServer, boot } from "@colyseus/testing";
 
// import your "app.config.ts" file here.
import appConfig from "../src/app.config";
 
describe("testing your Colyseus app", () => {
  let colyseus: ColyseusTestServer;
 
  before(async () => colyseus = await boot(appConfig));
  after(async () => colyseus.shutdown());
 
  beforeEach(async () => await colyseus.cleanup());
 
  it("connecting into a room", async() => {
    // `room` is the backend Room instance reference.
    const room = await colyseus.createRoom("my_room", {});
 
    // `client1` is the frontend `Room` instance reference (from the JavaScript SDK)
    const client1 = await colyseus.connectTo(room);
 
    // make your assertions
    assert.strictEqual(client1.sessionId, room.clients[0].sessionId);
  });
});

Then run your tests:

Terminal
npx vitest

Best Practices

  • Assert on both server and client state after simulating message exchange.
  • Create a fresh room in each test — rooms are disposed between tests via colyseus.cleanup(), so do not reuse room instances.
  • Always declare test cases with async.
  • Await for the server or client to process pending actions before running assertions.

Server-side Utilities

When sending messages between server and client, wait for the server to process the message before making assertions.

Wait For Message Type

Wait for a particular message type to arrive in the server.

App.test.ts
it("should receive message", async() => {
    const room = await colyseus.createRoom("my_room");
    const client1 = await colyseus.connectTo(room);
 
    client1.send("foo", "payload");
 
    // wait for a specific message
    const [ client, message ] = await room.waitForMessage("foo");
 
    assert.strictEqual(client.sessionId, client1.sessionId);
    assert.strictEqual("payload", message);
});

Wait For Next Message

Wait for any next message to arrive in the server.

Parameters

  • delay: number - additional delay after onMessage has been called in the backend, in milliseconds (optional)
App.test.ts
it("should receive message", async() => {
    const room = await colyseus.createRoom("my_room");
    const client1 = await colyseus.connectTo(room);
 
    let received = false;
    room.onMessage("foo", (client, message) => {
        received = true;
    });
 
    client1.send("foo");
    await room.waitForNextMessage();
 
    assert.ok(received);
});

Wait For Next Patch

Wait for the server to send the latest patched state to all clients.

App.test.ts
it("client state must match server's after patch is received", async() => {
    const room = await colyseus.createRoom("my_room");
    const client1 = await colyseus.connectTo(room);
 
    await room.waitForNextPatch();
 
    assert.deepStrictEqual(client1.state.toJSON(), room.state.toJSON());
});

Wait For Next Simulation Tick

Wait for the next simulation tick to complete.

App.test.ts
it("should assert something after room's simulation tick", async() => {
    const room = await colyseus.createRoom("my_room");
    const client1 = await colyseus.connectTo(room);
 
    await room.waitForNextSimulationTick();
 
    // assuming the room's state has a "tick" property that updates during setSimulationInterval()
    assert.strictEqual(room.state.tick, 1);
});

Client-side Utilities

You can call any frontend SDK methods through colyseus.sdk to simulate client behavior:

App.test.ts
it("should connect into battle_room with options x, y, z", async () => {
    const client = await colyseus.sdk.joinOrCreate("battle_room", {
        a: "a",
        b: "b",
        c: "c"
    });
    assert.ok(client.sessionId);
});

Wait for Next Patch

Wait for client state to be in sync with the server.

App.test.ts
it("should do xyz after receiving message 'x'", async () => {
    const client = await colyseus.sdk.joinOrCreate("battle_room");
    await client.waitForNextPatch();
    // perform assertions after client has received a message
});

Wait for Message Type

Wait for a particular message type to arrive in the client.

App.test.ts
it("should do xyz after receiving message 'x'", async () => {
    const client = await colyseus.sdk.joinOrCreate("battle_room");
    client.send("ask-for-x");
 
    await client.waitForMessage("received-x");
    // perform assertions after client has received "received-x" message type.
});

Wait for Next Message

Wait for any next message to arrive in the client.

App.test.ts
it("should do xyz after receiving message 'x'", async () => {
    const client = await colyseus.sdk.joinOrCreate("battle_room");
    client.send("ask-for-x");
 
    await client.waitForNextMessage();
    // perform assertions after client has received a message
});

Testing HTTP Routes

The @colyseus/testing package also provides an HTTP client for testing your custom HTTP routes:

  • colyseus.http.get(url, opts)
  • colyseus.http.post(url, opts)
  • colyseus.http.patch(url, opts)
  • colyseus.http.delete(url, opts)
  • colyseus.http.put(url, opts)
App.test.ts
it("should get json data", async () => {
    const response = await colyseus.http.get("/");
 
    // "data" is the response body
    assert.deepStrictEqual({ success: true }, response.data);
 
    // access to response headers.
    assert.strictEqual('header value', response.headers['some-header']);
});