'Typescript, React & Socket.io-client Tests

I'm trying to figure out how to write a Typescript/React app which uses socket.io to communicate to a server and thus other clients. However I'd like to write some tests in doing so.

In my sample app I have:

    import io, { Socket } from 'socket.io-client';
    
    const App = () => {
      let socket: Socket;
      const ENDPOINT = 'localhost:5000';
    
      const join = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
        event.preventDefault();
        socket = io(ENDPOINT);
        socket.emit('join', { name: 'Paola', room: '1' }, () => {});
      };
    
      return (
        <div className="join-container">
          <button className="join-button" onClick={join} data-testid={'join-button'}>
            Sign in
          </button>
        </div>
      );
    };
    
    export default App;

And my test looks like:

    import App from './App';
    import { render, screen, fireEvent } from '@testing-library/react';
    import 'setimmediate';
    
    
    describe('Join', () => {
      let mockEmitter = jest.fn();
      beforeEach(() => {
        jest.mock('socket.io-client', () => {
          const mockedSocket = {
            emit: mockEmitter,
            on: jest.fn((event: string, callback: Function) => {}),
          };
          return jest.fn(() => {
            return mockedSocket;
          }
            );
        });
      });
    
      afterEach(() => {
        jest.clearAllMocks();
      });
    
      it('joins a chat',  () => {
        // Arrange
        render(<App />);
    
        const btn =  screen.getByTestId('join-button');
    
        // Act
        fireEvent.click(btn);
    
        // Assert
        expect(btn).toBeInTheDocument();
        expect(mockEmitter).toHaveBeenCalled();
      });
    });

I just want to make sure I can mock socket.io-client so that I can verify that messages are being sent to the client and that it (later) reacts to messages sent in.

However the test is failing and it doesn't seem to be using my mock.

Error: expect(jest.fn()).toHaveBeenCalled()

Expected number of calls: >= 1
Received number of calls:    0


Solution 1:[1]

In the manual-mocks#examples doc, there is a note:

Note: In order to mock properly, Jest needs jest.mock('moduleName') to be in the same scope as the require/import statement.

So, there are two solutions:

app.tsx:

import React from 'react';
import io, { Socket } from 'socket.io-client';

const App = () => {
  let socket: Socket;
  const ENDPOINT = 'localhost:5000';

  const join = (event: React.MouseEvent<HTMLButtonElement>) => {
    event.preventDefault();
    socket = io(ENDPOINT);
    socket.emit('join', { name: 'Paola', room: '1' }, () => {});
  };

  return (
    <div className="join-container">
      <button className="join-button" onClick={join} data-testid={'join-button'}>
        Sign in
      </button>
    </div>
  );
};

export default App;

Option 1: Call jest.mock and import ./app module in module scope of the test file.

app.test.tsx:

import App from './App';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import React from 'react';

let mockEmitter = jest.fn();
jest.mock('socket.io-client', () => {
  return jest.fn(() => ({
    emit: mockEmitter,
    on: jest.fn(),
  }));
});

describe('Join', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  it('joins a chat', () => {
    // Arrange
    render(<App />);
    const btn = screen.getByTestId('join-button');

    // Act
    fireEvent.click(btn);

    // Assert
    expect(btn).toBeInTheDocument();
    expect(mockEmitter).toHaveBeenCalled();
  });
});

Option 2: Since you call the jest.mock in beforeEach hook, require the './app' module in beforeEach hook function scope as well.

app.test.tsx:

import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import React from 'react';

describe('Join', () => {
  let mockEmitter = jest.fn();
  let App;
  beforeEach(() => {
    App = require('./app').default;
    jest.mock('socket.io-client', () => {
      const mockedSocket = {
        emit: mockEmitter,
        on: jest.fn(),
      };
      return jest.fn(() => mockedSocket);
    });
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  it('joins a chat', () => {
    // Arrange
    render(<App />);
    const btn = screen.getByTestId('join-button');

    // Act
    fireEvent.click(btn);

    // Assert
    expect(btn).toBeInTheDocument();
    expect(fakeEmitter).toHaveBeenCalled();
  });
});

package version:

"jest": "^26.6.3",
"ts-jest": "^26.4.4"

jest.config.js:

module.exports = {
  preset: 'ts-jest/presets/js-with-ts',
  testEnvironment: 'jsdom'
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 slideshowp2