Skip to content

SimCord

The discord.py testing framework — simulate Discord, test your bot offline.

Run your real, unmodified bot against a virtual, in-memory Discord — no network, no tokens, and no Terms of Service concerns, because nothing ever connects to Discord. Simulate users sending messages, invoking slash commands and clicking buttons, then assert on exactly what your bot did.

async def test_ping(simcord_env):
    channel = simcord_env.create_guild().create_text_channel("general")
    alice = simcord_env.guild.add_member(simcord_env.create_user("alice"))

    await alice.send(channel, "!ping")          # full gateway round trip

    assert channel.last_message.content == "Pong!"

Get started in 5 minutes Browse the API

Stable API

The public surface is settled and follows semantic versioning as of 1.0 — see Stability & versioning. The parity matrix records exactly what is implemented; the remaining routes are a deliberate, demand-driven backlog that always fails loudly. SimCord never silently fakes success.

Why SimCord?

Unit tests cover your business logic, but the bugs that bite Discord bots live in the glue: converters, checks, permissions, forgotten tree.sync() calls, double-acknowledged interactions, oversized embeds. Until now the only way to test that layer was manually, in a real server. SimCord runs all of discord.py's real machinery — its parsers, cache, command frameworks and views — against a faithful mock of Discord's REST API and gateway, entirely in-process.

  • 🎯 Real discord.py semantics


    Server-side permission checks with authentic error codes (50013 Missing Permissions…), interaction lifecycle rules (40060 on double-ack), role hierarchy, timeouts, ephemeral visibility and validation limits.

  • 🐛 Real bugs caught


    Invoking a never-synced slash command fails your test, just like production. Clicking a disabled button is impossible, just like the client. Unhandled bot errors fail the test by default.

  • ⚡ Fast & deterministic


    No sleeps, no network, reproducible IDs and timestamps. The framework tracks the bot's tasks and settles after every action — there is never an asyncio.sleep in your tests.

  • ⏩ Time control


    env.advance_time(180) fires view timeouts and resets cooldowns instantly — no real waiting.

  • 🔍 Debuggable failures


    Failing tests automatically include a transcript of every gateway event and REST call — exactly what your bot did, in order.

  • 📢 Honest about gaps


    Anything not implemented raises RouteNotImplemented naming the route. Never a silent fake success. See the parity matrix.

How it works

discord.py has two narrow seams — every REST call goes through one HTTPClient.request, and every gateway event enters through one ConnectionState.parsers dispatch. SimCord swaps the transports behind those two seams for an in-memory backend and injects Discord-shaped payloads. Everything in between — models, converters, the command tree, checks, views, the cache — is real discord.py code running unmodified. Read the full architecture.

test ──► builders / actors ──► virtual backend (single source of truth)
                                   │                     │
                  gateway payloads ▼                     ▼ REST responses
                  ConnectionState.parsers        FakeHTTPClient route table
                                   │                     ▲
                                   ▼                     │
                                  your real, unmodified bot

Where to go next

  • Installation


    Requirements, the pytest extra, and version compatibility.

  • Quickstart


    From pip install to your first passing test in five minutes.

  • Core concepts


    Builders, actors and queries — the whole mental model on one page.

  • Guides


    Every feature, end to end, with runnable examples.

  • Recipes


    Copy-paste patterns for the tests you actually need to write.

  • Migrating from dpytest


    A direct concept-by-concept mapping.