How to Mock the ws NPM Package in Jest (The Right Way)

Testing WebSocket-based apps can feel like debugging through a keyhole—especially when you're working with the popular ws library in Node.js. If you’ve ever tried writing Jest tests and found yourself asking "How do I mock the ws package in Jest?" — you're not alone.

In this article, I’ll walk you through how to properly mock the ws npm package using Jest, ensuring your WebSocket logic stays covered and your test suite stays sane.


💡 Why Mock the ws Package?

The ws package is a fast, simple WebSocket implementation for Node.js. While it's awesome in production, it introduces challenges in tests:

  • It tries to open real connections
  • It triggers async behavior that’s hard to control
  • It makes your tests flaky

Instead, you should mock the WebSocket class and control it directly.


📦 Project Setup

Make sure you have the necessary packages installed:

npm install --save ws
npm install --save-dev jest

Let’s say you have a file like websocket-client.js:

// websocket-client.js
const WebSocket = require('ws');

class WebSocketClient {
  constructor(url) {
    this.ws = new WebSocket(url);

    this.ws.on('open', () => {
      console.log('Connected');
    });

    this.ws.on('message', (msg) => {
      console.log('Message:', msg);
    });
  }

  sendMessage(message) {
    this.ws.send(message);
  }
}

module.exports = WebSocketClient;

🧪 How to Mock ws with Jest

Now, in your test file websocket-client.test.js, you'll want to mock the ws module.

✅ Step-by-Step Mocking

// __mocks__/ws.js
const WebSocketMock = jest.fn(() => ({
  on: jest.fn(),
  send: jest.fn(),
  close: jest.fn(),
}));

module.exports = WebSocketMock;

Jest automatically uses files from __mocks__ when using jest.mock('ws').

Now write your test

// websocket-client.test.js
jest.mock('ws'); // Automatically uses __mocks__/ws.js

const WebSocket = require('ws');
const WebSocketClient = require('./websocket-client');

describe('WebSocketClient', () => {
  it('should initialize and call send', () => {
    const client = new WebSocketClient('ws://localhost:8080');

    expect(WebSocket).toHaveBeenCalledWith('ws://localhost:8080');

    // You can inspect the instance created by the constructor mock
    const mockInstance = WebSocket.mock.results[0].value;

    client.sendMessage('hello');
    expect(mockInstance.send).toHaveBeenCalledWith('hello');
  });
});

🔍 Bonus: Simulate WebSocket Events

Need to simulate an incoming message or an open event?

Modify the mock to store the event callbacks:

// __mocks__/ws.js
const WebSocketMock = jest.fn(() => {
  const handlers = {};

  return {
    on: jest.fn((event, cb) => {
      handlers[event] = cb;
    }),
    send: jest.fn(),
    close: jest.fn(),
    __trigger: (event, ...args) => {
      if (handlers[event]) {
        handlers[event](...args);
      }
    }
  };
});

module.exports = WebSocketMock;

Then in your test:

it('should handle incoming message', () => {
  const client = new WebSocketClient('ws://localhost:8080');
  const instance = WebSocket.mock.results[0].value;

  const messageSpy = jest.spyOn(console, 'log').mockImplementation();

  instance.__trigger('message', 'test-message');

  expect(messageSpy).toHaveBeenCalledWith('Message:', 'test-message');

  messageSpy.mockRestore();
});

🧼 Conclusion

Mocking ws in Jest isn’t black magic—it’s just a matter of intercepting the constructor and simulating behavior. With the mock approach above, your WebSocket logic becomes:

  • Testable ✅
  • Predictable ✅
  • Maintainable ✅

If you found this guide helpful, don’t forget to share it with your dev team or star the repo you’re working on 😉.