A set of utilities and Jest matchers to help testing complex websocket interactions.
Examples: See examples in a sample redux-saga app
Mock Socket is a peer dependency and
needs to be installed alongside jest-websocket-mock:
npm install --save-dev jest-websocket-mock mock-socketjest-websocket-mock exposes a WS class that can instantiate mock websocket
servers that keep track of the messages they receive, and in turn
can send messages to connected clients.
import WS from "jest-websocket-mock";
// create a WS instance, listening on port 1234 on localhost
const server = new WS("ws://localhost:1234");
// real clients can connect
const client = new WebSocket("ws://localhost:1234");
await server.connected; // wait for the server to have established the connection
// the mock websocket server will record all the messages it receives
client.send("hello");
// the mock websocket server can also send messages to all connected clients
server.send("hello everyone");
// ...simulate an error and close the connection
server.error();
// ...or gracefully close the connection
server.close();
// The WS class also has a static "clean" method to gracefully close all open connections,
// particularly useful to reset the environment between test runs.
WS.clean();The WS constructor also accepts an optional options object as second argument.
The only supported option is jsonProtocol: true, to tell the mock websocket
server to automatically serialize and deserialize JSON messages:
const server = new WS("ws://localhost:1234", { jsonProtocol: true });
server.send({ type: "GREETING", payload: "hello" });A WS instance has the following attributes:
connected: a Promise that resolves every time theWSinstance receives a new connection. The resolved value is theWebSocketinstance that initiated the connection.closed: a Promise that resolves every time a connection to aWSinstance is closed.nextMessage: a Promise that resolves every time aWSinstance receives a new message. The resolved value is the received message (deserialized as a JavaScript Object if theWSwas instantiated with the{ jsonProtocol: true }option).
send: send a message to all connected clients. (The message will be serialized from a JavaScript Object to a JSON string if theWSwas instantiated with the{ jsonProtocol: true }option).close: gracefully closes all opened connections.error: sends an error message to all connected clients and closes all opened connections.on: attach event listeners to handle newconnection,messageandcloseevents. The callback receives thesocketas its only argument.
jest-websocket-mock registers custom jest matchers to make assertions
on received messages easier:
.toReceiveMessage: async matcher that waits for the next message received by the the mock websocket server, and asserts its content. It will time out with a helpful message after 1000ms..toHaveReceivedMessages: synchronous matcher that checks that all the expected messages have been received by the mock websocket server.
test("the server keeps track of received messages, and yields them as they come in", async () => {
const server = new WS("ws://localhost:1234");
const client = new WebSocket("ws://localhost:1234");
await server.connected;
client.send("hello");
await expect(server).toReceiveMessage("hello");
expect(server).toHaveReceivedMessages(["hello"]);
});test("the mock server sends messages to connected clients", async () => {
const server = new WS("ws://localhost:1234");
const client1 = new WebSocket("ws://localhost:1234");
await server.connected;
const client2 = new WebSocket("ws://localhost:1234");
await server.connected;
const messages = { client1: [], client2: [] };
client1.onmessage = e => {
messages.client1.push(e.data);
};
client2.onmessage = e => {
messages.client2.push(e.data);
};
server.send("hello everyone");
expect(messages).toEqual({
client1: ["hello everyone"],
client2: ["hello everyone"],
});
});jest-websocket-mock can also automatically serialize and deserialize
JSON messages:
test("the mock server seamlessly handles JSON protocols", async () => {
const server = new WS("ws://localhost:1234", { jsonProtocol: true });
const client = new WebSocket("ws://localhost:1234");
await server.connected;
client.send(`{ "type": "GREETING", "payload": "hello" }`);
await expect(server).toReceiveMessage({ type: "GREETING", payload: "hello" });
expect(server).toHaveReceivedMessages([
{ type: "GREETING", payload: "hello" },
]);
let message = null;
client.onmessage = e => {
message = e.data;
};
server.send({ type: "CHITCHAT", payload: "Nice weather today" });
expect(message).toEqual(`{"type":"CHITCHAT","payload":"Nice weather today"}`);
});test("the mock server sends errors to connected clients", async () => {
const server = new WS("ws://localhost:1234");
const client = new WebSocket("ws://localhost:1234");
await server.connected;
let disconnected = false;
let error = null;
client.onclose = () => {
disconnected = true;
};
client.onerror = e => {
error = e;
};
server.send("hello everyone");
server.error();
expect(disconnected).toBe(true);
expect(error.origin).toBe("ws://localhost:1234/");
expect(error.type).toBe("error");
});it("the server can refuse connections", async () => {
const server = new WS("ws://localhost:1234");
server.on("connection", socket => {
socket.close({ wasClean: false, code: 1003, reason: "NOPE" });
});
const client = new WebSocket("ws://localhost:1234");
client.onclose = (event: CloseEvent) => {
expect(event.code).toBe(1003);
expect(event.wasClean).toBe(false);
expect(event.reason).toBe("NOPE");
};
expect(client.readyState).toBe(WebSocket.CONNECTING);
await server.connected;
expect(client.readyState).toBe(WebSocket.CLOSING);
await server.closed;
expect(client.readyState).toBe(WebSocket.CLOSED);
});You can set up a mock server and tear it down between test:
beforeEach(async () => {
ws = new WS("ws://localhost:1234");
await ws.connected;
ws.send("Connected!");
});
afterEach(() => {
WS.clean();
});jest-websocket-mock uses Mock Socket
under the hood to mock out WebSocket clients.
Out of the box, Mock Socket will only mock out the global WebSocket object.
If you are using a third-party WebSocket client library (eg. a Node.js
implementation, like ws), you'll need
to set up a manual mock:
- Create a
__mocks__folder in your project root - Add a new file in the
__mocks__folder named after the library you want to mock out. For instance, for thewslibrary:__mocks__/ws.js. - Export Mock Socket's implementation in-lieu of the normal export from the
library you want to mock out. For instance, for the
wslibrary:
// __mocks__/ws.js
export { WebSocket as default } from "mock-socket";For a real life example, see the examples directory, and in particular the saga tests.
See the contributing guide.