smocket mascot: a cool cat wearing sunglassessmocket

Mock Socket.IO without a server.

smocket reimplements Socket.IO's rooms, broadcasts, and acknowledgements in memory — and every release is verified against the real library.

Sweet setup, rocket speed.

  • v1.0.0
  • dual-run CI
  • browser-tested
a s’more rocket blasting off
Packed like a s’more, aimed like a rocket. Each message reaches exactly the sockets it was addressed to, and no others.

Three players, one page, no server.

One person draws. The other two watch the lines arrive and say what they think it is. Every stroke and every guess is a Socket.IO event, routed here in the page.

A recorded round, replayed.Take the pen

Use your Socket.IO handlers without starting a network server.

Node.js Socket.IO mock serverHTTP server, listening port, and cleanup.ABC
import { once } from 'node:events';
import { createServer } from 'node:http';
import type { AddressInfo } from 'node:net';
import { Server } from 'socket.io';
import { io as connect } from 'socket.io-client';

const httpServer = createServer();
const io = new Server(httpServer);
registerApplication(io);

httpServer.listen(0, '127.0.0.1');
await once(httpServer, 'listening');

const { port } = httpServer.address() as AddressInfo;
const url = `http://127.0.0.1:${port}`;
const a = connect(url);
const b = connect(url);
const c = connect(url);

// Then disconnect every client, close Socket.IO,
// and close the HTTP server after the test.
smocketThe same handlers, no listening server.ABC
import { Server } from 'smocket';
import { connect } from 'smocket-client';

const url = 'http://localhost:3000';
const io = new Server(url);
registerApplication(io);

const a = connect(url);
const b = connect(url);
const c = connect(url);

registerApplication(io) stays the same.

Built around Socket.IO behavior.

  • Delivery fidelity

    Rooms and socket ids live in the same bidirectional maps socket.io uses. Fan-out is a set operation, not a loop over guesses.

  • Checked against the real thing

    Oracle-backed conformance cases run against both Socket.IO and smocket. Browser, SharedWorker, and packaging checks cover smocket-specific behavior.

  • No server, no ports

    Nothing binds, nothing listens. Tests start and finish in the same process.

  • Honest about its limits

    What a mock cannot have, smocket does not pretend to have. The list is short and written down.

Switch in three steps.

  1. Install

    npm install -D smocket smocket-client
  2. Alias the client

    // vitest.config.ts
    import { defineConfig } from 'vitest/config';
    
    export default defineConfig({
      resolve: {
        alias: { 'socket.io-client': 'smocket-client' },
      },
    });
  3. Run your app code

    import { expect, it } from 'vitest';
    import { Server, type ServerSocketContract } from 'smocket';
    import { joinChat } from '../src/chat';
    
    it('delivers a room message to the other member', async () => {
      const url = 'http://localhost:3000';
      const io = new Server(url);
    
      io.on('connection', (socket: ServerSocketContract) => {
        socket.on('join', (room: string, ack: () => void) => {
          void socket.join(room);
          ack();
          socket.on('message', (text: string) => {
            socket.to(room).emit('message', text);
          });
        });
      });
    
      // joinChat still imports io from socket.io-client.
      const a = joinChat(url, 'alice', 'general');
      const b = joinChat(url, 'bob', 'general');
      await Promise.all([a.ready, b.ready]);
    
      const heard = new Promise((resolve) => b.onMessage(resolve));
      a.send('hello');
    
      await expect(heard).resolves.toBe('hello');
    });

See who received what.

Rooms, exclusions, and targeted emits follow the supported delivery semantics verified against Socket.IO. Here is the delivery record.

  • A
  • B
  • C
socket_A.to('room-1').emit('stroke', { … })
B, C (except A)
io.to(sid_A).emit('word', 'giraffe')
A
io.to('room-1').emit('chat', { … })
A, B, C

Scope

What smocket does

  • Rooms and namespaces
  • Broadcast, with and without exclusions
  • Targeted emits by socket id
  • Acknowledgements, including timeouts
  • Middleware, handshake, and per-socket data
  • Disconnect cleanup

What a mock cannot have

  • Reconnection behaviour — there is no "later" to wait for
  • Transport fallback — there is no transport
  • Heartbeat — there is no connection to check
  • Multi-server adapters — there is one process
  • Binary encoding — nothing is serialised
0%