How to mock phoenix sockets in Jest?

I want to test my js app that uses phoenix.js sockets. I want to write tests along the lines of:

  • Set up a mocked socket and channel.
  • Render my app.
  • Interact with the app.
  • Assert that a message was sent on the channel.
  • Mock a response to that message as if the server had replied.
  • Assert that the UI was updated with the response.

Is there a recommended way to do this?

I’m familiar with using jest to mock functions, but I’m struggling to set it up through the layers of callbacks and classes in a way that lets me assert something was pushed or send a reply.

Sorry, I’m a bit late to the party here.

Using vitest, I was able to accomplish creating a phoenix channel mock doing something like this:

import { vi } from 'vitest';
import * as phoenix from 'phoenix';

vi.mock('phoenix', () => {
  return {
    Socket: vi.fn().mockImplementation(() => fakeSocket)
  };
});

const fakeChannel = {
  join: vi.fn().mockReturnThis(),
  push: vi.fn().mockReturnThis(),
  on: vi.fn().mockReturnThis(),
  off: vi.fn().mockReturnThis(),
  leave: vi.fn().mockReturnThis(),
  receive: vi.fn().mockImplementation((event, callback) => {
    if (event === 'ok') {
      callback({});
    }

    return this;
  })
};

const fakeSocket = {
  connect: vi.fn(),
  channel: vi.fn(() => fakeChannel),
  // mock any other used Socket class methods here
};

Then inside of your tests, you can override the mocks as needed:


describe("Example", () => {
  it("can join a channel", async () => {
    await joinChannel(); // This is the `socket.channel(...).join().receive("ok", callback)` wrapped by a promise that resolves in the callback

    expect(fakeSocket.connect).toHaveBeenCalledOnce();
    expect(fakeChannel.join).toHaveBeenCalledOnce();
  });

  it("can receive pushed event", async () => {
    await joinChannel(); 

    fakeChannel.on.mockImplementation((event, handler) => {
      if (event == "pushed:event") handler("Some data");

      return fakeChannel;
    });

    const result = await listenForPushedEvent(); // This is the function that sets up `channel.on("pushed:event", callback)` wrapped in a promise that resolves in the callback

    expect(result).toEqual("Some data");
  });
});

The challenge still exists that you have to carefully craft your mocks, which can be difficult since the Phoenix JS API is callback based.