Skip to content
Advertisement

How to test code with Jest that uses readonly fields on mocks of libraries (such as ws socket.readyState)?

I want to unit test this JS code with Jest. It’s using the ws websocket library:

Socket.js

import WebSocket from "ws";

export default class Socket {
    socket = undefined;

    connect(url): void {
        if (this.socket !== undefined && this.socket.readyState === WebSocket.OPEN) {
            throw new Error("Socket is already connected");
        }

        this.socket = new WebSocket(url);
    }
}

I want to check the error is thrown. For that to happen, I create a mock for the socket and then need to set the readyState of the socket object to CLOSED. The issue is that this is impossible because it’s a readonly field.

Test:

import Socket from "./Socket";
import WebSocket from "ws";

jest.mock("ws");

it("can connect", () => {
    // Arrange
    const url = "www.someurl.com";
    jest.spyOn(global, "Promise").mockReturnValueOnce({} as Promise<void>);

    const sut = new Socket();

    // Act
    sut.connect(url);

    const webSocket = jest.mocked(WebSocket).mock.instances[0];

    // Set readystate of socket
    webSocket.readyState = WebSocket.CLOSED; // Error here: Cannot assign to 'readyState' because it is a read-only property. ts(2540)

    const action = () => sut.connect(url);

    // Assert
    expect(action).toThrowError(new Error("Socket is already connected"));
});

Question: How to test any code that uses readonly fields on mocks of libraries such as socket.readyState?

Edit: Added the mvce on request above.

Advertisement

Answer

The better test strategy is to create a test WebSocket server and connect it. This is also the official team doing, see some test cases. Mocking ws and testing the implementation details is not recommended.

Below testing code using TypeScript:

socket.ts:

import WebSocket from "ws";

export default class Socket {
  socket!: WebSocket;

  connect(url: string): void {
    if (this.socket !== undefined && this.socket.readyState === WebSocket.OPEN) {
      throw new Error("Socket is already connected");
    }
    this.socket = new WebSocket(url);
  }
}

socket.test.ts:

import WebSocket from "ws";
import Socket from "./socket";

describe('74062437', () => {

  test('should connect the websocket server', (done) => {
    const wss = new WebSocket.Server({ port: 0 }, () => {
      const socket = new Socket()
      socket.connect(`ws://localhost:${(wss.address() as WebSocket.AddressInfo).port}`);

      socket.socket.on('open', () => {
        expect(socket.socket.readyState).toEqual(WebSocket.OPEN);
        socket.socket.close();
      });

      socket.socket.on('close', () => wss.close(done));
    });
  });

  test('should throw error if already connected websocket server', (done) => {
    expect.assertions(2);
    const wss = new WebSocket.Server({ port: 0 }, () => {
      const socket = new Socket();
      socket.connect(`ws://localhost:${(wss.address() as WebSocket.AddressInfo).port}`);

      socket.socket.on('open', () => {
        expect(socket.socket.readyState).toEqual(WebSocket.OPEN);
        expect(() => socket.connect(`ws://localhost:${(wss.address() as WebSocket.AddressInfo).port}`)).toThrowError('Socket is already connected')
        socket.socket.close();
      });

      socket.socket.on('close', () => wss.close(done));
    });
  });
});

Test result:

 PASS  stackoverflow/74062437/socket.test.ts (10.964 s)
  74062437
    ✓ should connect the websocket server (31 ms)
    ✓ should throw error if already connected websocket server (6 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 socket.ts |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        11.784 s

package versions:

"ws": "^8.9.0",
"jest": "^26.6.3"
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement