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 (40060on 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.sleepin 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
RouteNotImplementednaming 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¶
-
Requirements, the
pytestextra, and version compatibility. -
From
pip installto your first passing test in five minutes. -
Builders, actors and queries — the whole mental model on one page.
-
Every feature, end to end, with runnable examples.
-
Copy-paste patterns for the tests you actually need to write.
-
A direct concept-by-concept mapping.