npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@erox/mock-gateway

v0.2.0

Published

Fake Discord gateway and REST server for testing bots without hitting the real API

Downloads

18

Readme

@discord-toolkit/mock-gateway

A fake Discord gateway + REST server you can run locally so you can actually unit test bot logic without hitting the real API, burning rate limits, or needing a live token in CI.

Models gateway v10 connection behavior: HELLO/IDENTIFY/READY handshake, heartbeat acks, RESUME/RESUMED, and the 4002 close code for oversized payloads.

Why

Testing Discord bots normally means either mocking discord.js internals by hand (brittle, breaks every update) or just not testing the bot logic at all and hoping for the best. This spins up a real WebSocket server that speaks the gateway protocol closely enough to fool a client, plus an HTTP server that stands in for discord.com/api, so you point your bot at localhost during tests and everything just works.

Install

npm install --save-dev @discord-toolkit/mock-gateway

Quick start

const { MockDiscordServer } = require('@discord-toolkit/mock-gateway');

const server = new MockDiscordServer();
const { gatewayUrl, restUrl } = await server.start();

// point your bot / client at these instead of the real Discord URLs

// register a canned REST response
server.rest.on('POST', '/api/v10/channels/123/messages', (req) => ({
  status: 200,
  body: { id: '999', content: req.body.content },
}));

// simulate an incoming message
server.emitMessage({ content: '!ping', channelId: '123' });

// later, assert your bot actually replied
server.hasSentMessage('123'); // true
server.sentMessages('123');   // [{ content: '...' }, ...]

await server.stop();

Beginner guide: what this is actually doing

If you've never tested a Discord bot before, here's why this exists and how it fits into a test:

A Discord bot normally works like this: it opens a WebSocket connection to Discord's real gateway, gets sent events (someone posted a message, someone joined a voice channel, etc), and reacts by making REST calls back to Discord's real API (send a reply, add a role, whatever).

Testing that against the real Discord API is a bad idea — you'd need a live bot token, a real server to test in, and every test run would be slow and could get you rate limited or banned for spam.

This package gives you two fake servers that run locally:

  • a fake gateway (WebSocket server) that your bot connects to instead of Discord's real one — it plays along with the connection handshake, and you can make it emit fake events (messages, interactions, whatever) whenever you want
  • a fake REST API (HTTP server) that your bot's outgoing calls hit instead of discord.com/api — you decide what it responds with, and it records everything that was called so you can check your bot's behavior afterward

So a typical test looks like: start the fake servers → point your bot at them → simulate an incoming event → check that your bot made the REST call you expected. No real Discord connection anywhere in the test.


API reference

new MockDiscordServer(options)

Creates both the fake gateway and fake REST server together.

const server = new MockDiscordServer({
  gateway: { port: 0 }, // options passed to MockGateway
  rest: { port: 0 },    // options passed to MockRest
});

Ports default to 0, meaning "pick a free port" — good for running tests in parallel without collisions. You get the actual assigned ports back from server.start().

server.start()

Starts both servers. Returns:

{
  gatewayUrl: 'ws://localhost:PORT',
  restUrl: 'http://localhost:PORT',
}

Point your bot's client at these instead of Discord's real endpoints.

server.stop()

Shuts both servers down. Call this in your test teardown (afterEach or equivalent) so ports get freed between tests.


Gateway events — which method for which event

The fake gateway handles the connection handshake automatically:

  • On connect, it sends HELLO (op 10) like the real gateway does.
  • When your client sends IDENTIFY (op 2), it replies with READY.
  • When your client sends RESUME (op 6), it replies with RESUMED — useful for testing that your client's reconnect logic actually works, though this mock doesn't replay missed events like the real gateway does.
  • When your client sends a HEARTBEAT (op 1), it acks it (op 11).
  • If a client sends a payload over 4096 bytes, the connection closes with code 4002, matching real gateway behavior — good for catching bugs where large payloads (e.g. huge presence updates) aren't chunked.

Beyond that, you control what events get sent using these:

| method | event fired | when to use it | |---|---|---| | server.emitMessage(overrides) | MESSAGE_CREATE | simulating a user sending a message | | server.emitInteraction(overrides) | INTERACTION_CREATE | simulating a slash command / button / select menu use | | server.gateway.emit(eventName, data) | anything | any other event — GUILD_MEMBER_ADD, VOICE_STATE_UPDATE, MESSAGE_REACTION_ADD, etc |

emitMessage(overrides)

server.emitMessage({
  content: '!ping',
  channelId: '333333333333333333',
  guildId: '555555555555555555',
  author: { id: '222222222222222222', username: 'someone', bot: false },
});

All fields have sane defaults if you don't pass them, so server.emitMessage({ content: 'hi' }) alone works fine for simple cases.

emitInteraction(overrides)

server.emitInteraction({
  type: 2, // 2 = APPLICATION_COMMAND, 3 = MESSAGE_COMPONENT, 5 = MODAL_SUBMIT
  data: { name: 'ping' },
  channelId: '333333333333333333',
  guildId: '555555555555555555',
});

Same deal — defaults fill in the rest so you only need to override what matters for your test.

gateway.emit(eventName, data)

For anything not covered by the two helpers above, drop down to the raw gateway and emit whatever event name + payload shape you need. Use Discord's own gateway event docs for the payload shape of whichever event you're simulating — this just delivers whatever data object you give it under that event name.

server.gateway.emit('GUILD_MEMBER_ADD', {
  guild_id: '555555555555555555',
  user: { id: '222222222222222222', username: 'newperson' },
});

REST mocking — registering and checking calls

server.rest.on(method, path, handler)

Registers what the fake REST server should respond with for a given route. path should match exactly what your client will request, including the /api/v10 prefix if your client sends it.

handler can be:

  • a plain object — used directly as the JSON response body, status 200
  • a function — receives { method, path, body } from the incoming request and returns { status, body, headers }
// static response
server.rest.on('GET', '/api/v10/users/@me', {
  id: '000000000000000000',
  username: 'mock-bot',
});

// dynamic response based on what was sent
server.rest.on('POST', '/api/v10/channels/123/messages', (req) => ({
  status: 200,
  body: { id: '999', content: req.body.content, channel_id: '123' },
}));

Any route you don't register returns a 404 with a message telling you which route was missing — useful for catching bot behavior you didn't expect during a test.

Checking what your bot actually did

| method | returns | |---|---| | server.rest.wasCalled(method, path) | true/false | | server.rest.callsTo(method, path) | array of { method, path, body, headers } for every matching call | | server.rest.reset() | clears recorded calls (call between tests if reusing one server instance) |

Shortcuts specifically for messages:

server.hasSentMessage('333333333333333333');   // true/false
server.sentMessages('333333333333333333');      // array of message bodies sent

Full example: testing a ping command

const { MockDiscordServer } = require('@discord-toolkit/mock-gateway');
const { startMyBot } = require('../bot'); // your actual bot code

test('bot replies pong to !ping', async () => {
  const server = new MockDiscordServer();
  const { gatewayUrl, restUrl } = await server.start();

  server.rest.on('POST', '/api/v10/channels/333333333333333333/messages', (req) => ({
    status: 200,
    body: { id: '1', content: req.body.content },
  }));

  const bot = await startMyBot({ gatewayUrl, restUrl }); // wire your bot to these URLs

  server.emitMessage({ content: '!ping', channelId: '333333333333333333' });

  // give the bot a tick to process and respond
  await new Promise((r) => setTimeout(r, 50));

  assert.ok(server.hasSentMessage('333333333333333333'));
  assert.strictEqual(server.sentMessages('333333333333333333')[0].content, 'pong');

  await bot.destroy();
  await server.stop();
});

Common mistakes

  • Forgetting to register a REST route the bot actually calls. You'll get a 404 in the fake server and your bot will likely error or silently do nothing — check server.rest.calls if a test isn't behaving.
  • Not awaiting a tick after emitting an event. Your bot processes events asynchronously; emitting a message and immediately asserting can race ahead of your bot's handler. Add a short delay or, better, hook into your bot's own "done processing" signal if it has one.
  • Reusing one server across tests without resetting. Call server.rest.reset() between tests (or spin up a fresh MockDiscordServer per test) so call history doesn't leak between them.

Notes

  • This isn't a full reimplementation of the gateway protocol — it covers what's needed to get a client through HELLO/IDENTIFY/READY and then push events at it. If your client does something more exotic (resume logic, sharding handshakes), you may need to extend MockGateway.
  • Ports default to random free ports (0), so tests can run in parallel without clashing.
  • Pairs well with @discord-toolkit/rate-limiter — you can register a 429 response on a route in server.rest.on(...) to test your retry logic against simulated rate limits too.

Changelog

0.2.0

  • Added RESUME (op 6) → RESUMED handling for testing reconnect logic.
  • Oversized payloads (>4096 bytes) now close the socket with 4002, matching real gateway behavior.

0.1.0

  • Initial release: fake gateway (HELLO/IDENTIFY/READY/heartbeat), fake REST server with route registration and call recording.