3

JavaScript testing #14. Mocking WebSockets using the mock-socket library

 1 year ago
source link: https://wanago.io/2022/08/08/javascript-testing-mocking-websockets-mock-socket/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

JavaScript testing #14. Mocking WebSockets using the mock-socket library

React Testing

August 8, 2022

This entry is part 14 of 14 in the JavaScript testing tutorial

Every automated test that we write should be deterministic. One of the things we need to do to achieve that is to make our frontend tests independent of the backend. Besides mocking REST API requests, we also need to learn how to mock a WebSocket connection. In this article, we create a simple backend application and use React to connect to it and render data. We also write unit tests that ensure that our frontend application works correctly.

Creating a simple application with WebSockets

Let’s start by defining a simple application that uses WebSockets:

  • the frontend application establishes a connection with the WebSocket server and sends a LogsRequested message,
  • the server receives the LogsRequested and starts sending logs,
  • the server sends the NoMoreLogs message if there are no more logs,
  • the frontend application closes the WebSocket connection as soon as it receives the NoMoreLogs message.

First, we need to define the format of the messages our WebSocket server sends.

ServerMessage.ts
export enum ServerMessageType {
  Log = 'Log',
  NoMoreLogs = 'NoMoreLogs',
export interface ServerMessage {
  id: number;
  type: ServerMessageType;
  content: string;

Besides the above, we also need to specify the messages that our frontend application can send.

ClientMessage.ts
export enum ClientMessageType {
  LogsRequested = 'LogsRequested',
export interface ClientMessage {
  type: ClientMessageType;

Feel free to add more types of message types if your applicatio needs it.

Creating the WebSocket server

Once we have the messages defined, we can create the WebSocket server we will mock later.

server.ts
import { Server } from 'ws';
import { ServerMessage, ServerMessageType } from './ServerMessage';
import { ClientMessage, ClientMessageType } from './ClientMessage';
const webSocketServer = new Server({
  port: 5000,
const messages: ServerMessage[] = [
    id: 1,
    type: ServerMessageType.Log,
    content: 'Log #1',
    id: 2,
    type: ServerMessageType.Log,
    content: 'Log #2',
    id: 3,
    type: ServerMessageType.NoMoreLogs,
    content: 'There will be no more logs',
webSocketServer.on('connection', (socket) => {
  socket.on('message', (message) => {
    const messageData: ClientMessage = JSON.parse(message.toString());
    if (messageData.type === ClientMessageType.LogsRequested) {
      messages.forEach((message) => {
        socket.send(JSON.stringify(message));

If you want to know more about WebSockets, check out Introduction to WebSockets. Creating a Node.js server and using WebSocket API in the browser

Above, we send all the messages one by one as soon as the client sends the LogsRequested message.

Creating the frontend application

In our frontend application, we aim to receive all of the logs and render them.

LogsList.tsx
import React from 'react';
import useLogs from './useLogs';
const LogsList = () => {
  const { messages } = useLogs();
  return (
    <div>
      {messages.map((message) => (
        <div key={message.id}>{message.content}</div>
    </div>
export default Logs;

To connect to the server, we need to add its URL to the environment variables.

REACT_APP_WEB_SOCKET_URL=ws://localhost:5000

In this simple example, we use Create React App. With CRA, all custom environment variables have to start with REACT_APP.

For the above variable to work well with TypeScript, let’s declare it in the react-app-env.d.ts file.

react-app-env.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    REACT_APP_WEB_SOCKET_URL: string;

Under the hood of our component, we want to connect to the WebSocket server and receive the logs.

useLogsList.tsx
import { useEffect, useState } from 'react';
import { ServerMessage, ServerMessageType } from './ServerMessage';
import { ClientMessage, ClientMessageType } from './ClientMessage';
function useLogsList() {
  const [messages, setMessages] = useState<ServerMessage[]>([]);
  const handleConnection = (webSocket: WebSocket) => {
    const requestLogsMessage: ClientMessage = {
      type: ClientMessageType.LogsRequested,
    webSocket.send(JSON.stringify(requestLogsMessage));
  const handleIncomingMessage = (
    webSocket: WebSocket,
    message: MessageEvent,
  ) => {
    const messageData: ServerMessage = JSON.parse(message.data);
    setMessages((currentMessages) => {
      return [...currentMessages, messageData];
    if (messageData.type === ServerMessageType.NoMoreLogs) {
      webSocket.close();
  useEffect(() => {
    const webSocket = new WebSocket(process.env.REACT_APP_WEB_SOCKET_URL);
    setMessages([]);
    webSocket.addEventListener('open', () => {
      handleConnection(webSocket);
    webSocket.addEventListener('message', (message) => {
      handleIncomingMessage(webSocket, message);
    return () => {
      webSocket.close();
  }, []);
  return {
    messages,
export default useLogsList;

Doing the above causes our React application to establish a WebSocket connection and render all the logs.

Testing our frontend application with mock-socket

To test our frontend application, we must first install the mock-socket library.

npm install mock-socket

To be able to mock the WebSocket connection, we need to create an instance of the Server.

import { Server } from 'mock-socket';
const websocketServer = new Server('ws://localhost:5000');

The mock-socket library also has the socket.io support.

The Server class mimics the WebSocket API we’ve used in Node.js. Thanks to that, it is straightforward to use.

webSocketServer.on('connection', (socket) => {
  socket.on('message', (message) => {
    console.log('Received a message from the client', message);
  socket.send('Sending a message to the client');

Once we don’t need our server anymore, we can call websocketServer.close().

Let’s write a simple test combining all of the above knowledge.

LogsList.test.tsx
import { Server } from 'mock-socket';
import { ClientMessage, ClientMessageType } from './ClientMessage';
import { ServerMessage, ServerMessageType } from './ServerMessage';
import LogsList from './LogsList';
import { render, waitFor } from '@testing-library/react';
describe('The LogsList component', () => {
  let websocketServer: Server;
  let serverMessages: ServerMessage[];
  beforeEach(() => {
    serverMessages = [];
    websocketServer = new Server(process.env.REACT_APP_WEB_SOCKET_URL);
    websocketServer.on('connection', (socket) => {
      socket.on('message', (message) => {
        if (typeof message !== 'string') {
          return;
        const parsedData: ClientMessage = JSON.parse(message);
        if (parsedData.type === ClientMessageType.LogsRequested) {
          serverMessages.forEach((message) => {
            socket.send(JSON.stringify(message));
  afterEach(() => {
    websocketServer.close();
  describe('if the WebSocket sends messages and announces the end of logs', () => {
    beforeEach(() => {
      serverMessages = [
          id: 1,
          type: ServerMessageType.Log,
          content: 'Log #1',
          id: 2,
          type: ServerMessageType.Log,
          content: 'Log #2',
          id: 3,
          type: ServerMessageType.NoMoreLogs,
          content: 'There will be no more logs',
    it('should render all of the logs', async () => {
      const logsList = render(<LogsList />);
      for (const message of serverMessages) {
        await logsList.findByText(message.content);

PASS src/LogsList/LogsList.test.tsx
The LogsList component
if the WebSocket sends messages and announces the end of logs
✓ should render all of the logs

Testing if the WebSocket connection was closed

We want our frontend application to close the WebSocket connection as soon as it receives the NoMoreLogs message.

To test the above, we can take advantage of the clients() function that returns the array of all connected clients.

LogsList.test.tsx
describe('if the WebSocket sends messages and announces the end of logs', () => {
  beforeEach(() => {
    serverMessages = [
        id: 1,
        type: ServerMessageType.Log,
        content: 'Log #1',
        id: 2,
        type: ServerMessageType.Log,
        content: 'Log #2',
        id: 3,
        type: ServerMessageType.NoMoreLogs,
        content: 'There will be no more logs',
  it('should close the WebSocket connection', () => {
    render(<LogsList />);
    return waitFor(() => {
      return expect(websocketServer.clients().length).toBe(0);
  // ...

PASS src/LogsList/LogsList.test.tsx
The LogsList component
if the WebSocket sends messages and announces the end of logs
✓ should render all of the logs
✓ should close the WebSocket connection

Above, we use the waitFor function because our code is asynchronous, and the application doesn’t close the connection immediately.

There is another case in which we want our application to close the WebSocket connection. We want it to happen when the application doesn’t receive the NoMoreLogs message, but the component is unmounted.

LogsList.test.tsx
describe('if the WebSocket sends messages and does not announce the end of logs', () => {
  beforeEach(() => {
    serverMessages = [
        id: 1,
        type: ServerMessageType.Log,
        content: 'Log #1',
        id: 2,
        type: ServerMessageType.Log,
        content: 'Log #2',
  it('should render all of the logs', async () => {
    const logsList = render(<LogsList />);
    for (const message of messages) {
      await logsList.findByText(message.content);
  describe('and when the component is unmounted', () => {
    it('should close the WebSocket connection', () => {
      const logsList = render(<LogsList />);
      logsList.unmount();
      return waitFor(() => {
        return expect(websocketServer.clients().length).toBe(0);

PASS src/LogsList/LogsList.test.tsx
The LogsList component
if the WebSocket sends messages and does not announce the end of logs
✓ should render all of the logs
and when the component is unmounted
✓ should close the WebSocket connection

Testing the messages sent by the client

We might want to test if the frontend application sends the correct messages to our WebSocket server. To do the above, we can declare an array of the client messages and update it when the message arrives.

import { Server } from 'mock-socket';
import { ClientMessage, ClientMessageType } from './ClientMessage';
import { ServerMessage, ServerMessageType } from './ServerMessage';
import LogsList from './LogsList';
import { render, waitFor } from '@testing-library/react';
describe('The LogsList component', () => {
  let websocketServer: Server;
  let serverMessages: ServerMessage[];
  let clientMessages: ClientMessage[];
  beforeEach(() => {
    serverMessages = [];
    clientMessages = [];
    websocketServer = new Server(process.env.REACT_APP_WEB_SOCKET_URL);
    websocketServer.on('connection', (socket) => {
      socket.on('message', (message) => {
        if (typeof message !== 'string') {
          return;
        const parsedData: ClientMessage = JSON.parse(message);
        clientMessages.push(parsedData);
        if (parsedData.type === ClientMessageType.LogsRequested) {
          serverMessages.forEach((message) => {
            socket.send(JSON.stringify(message));
  afterEach(() => {
    websocketServer.close();
  it('should send the message requesting the logs', () => {
    render(<LogsList />);
    return waitFor(async () => {
      await expect(clientMessages.length).toBe(1);
      await expect(clientMessages[0].type).toBe(
        ClientMessageType.LogsRequested,
  // ...

Summary

In this article, we’ve created a simple Node.js server and a frontend application that connects to it with a WebSocket connection. We’ve tested if the React application correctly interprets the messages sent by the server. Besides that, we’ve also tested if the client sends the expected messages and closes the connection when appropriate. All of the above gave us a solid grasp on how to use the mock-socket library to test frontend applications that use a WebSocket connection.

Series Navigation<< JavaScript testing #13. Mocking a REST API with the Mock Service Worker


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK