Skip to main content

SimCord — discord.py testing framework. Simulate Discord, test your bot offline: no network, no token, no ToS.

Project description

SimCord

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

Test your discord.py bot with a full virtual Discord environment: no network, no token, no test server, no Terms of Service concerns. SimCord is the missing testing library for discord.py bots.

CI Docs PyPI Python discord.py License: MIT

Quickstart · Documentation · Parity matrix · Contributing


SimCord is a discord.py testing framework that gives your bot a fake but faithful Discord to run against. Simulate users sending messages, invoking slash commands, clicking buttons and submitting modals — 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!"

⚠️ Alpha. The core surface (messages, prefix commands, slash commands, components, modals, permissions, reactions, threads, DMs, time control) works; see the parity matrix for the long tail. Unimplemented routes always fail 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, 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.
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.
📢 Loud gaps Anything not implemented raises RouteNotImplemented naming the route. Never silent fake success.

Install

pip install simcord[pytest]

Requires Python 3.11+ and discord.py 2.7+. Zero dependencies beyond discord.py itself.

Quickstart

Tell the bundled pytest plugin how to build your bot:

# conftest.py
import pytest
from mybot import create_bot   # however your project builds its commands.Bot

@pytest.fixture
def simcord_bot():
    return create_bot()

Then write tests against the simcord_env fixture:

import discord

async def test_ban_slash_command(simcord_env):
    guild = simcord_env.create_guild()
    channel = guild.create_text_channel("mod")
    mods = guild.create_role("Mods", permissions=discord.Permissions(ban_members=True))
    mod = guild.add_member(simcord_env.create_user("mod"), roles=[mods])
    target = guild.add_member(simcord_env.create_user("spammer"))

    result = await mod.slash(channel, "ban", user=target, reason="spam")

    assert result.ephemeral
    assert result.response.content == f"Banned {target.mention}: spam"
    assert guild.get_ban(target) is not None

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

    result = await alice.slash(channel, "offer")    # bot replies with a View(timeout=180)
    await simcord_env.advance_time(180)             # instant — the view times out

    assert "expired" in channel.last_message.content

Buttons, selects, modals, context menus, autocomplete, reactions, threads, DMs, fault injection and more: see the documentation. Prefer explicit control? async with simcord.run(bot) as env: works in any async test framework.

How it works

discord.py has two narrow seams: every REST call funnels through HTTPClient.request, and every gateway event enters through ConnectionState.parsers. SimCord replaces the first with a fake routed to an in-memory backend (a single source of truth for guilds, channels, members, messages, commands and interactions) and injects Discord-shaped payloads through the second. Everything between those seams — which is everything your bot touches — is real discord.py code running unmodified.

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

Details in the architecture docs.

discord.py testing — common use cases

SimCord covers the full range of discord.py bot testing scenarios:

  • discord.py unit testing — test individual commands in isolation
  • discord.py integration testing — test full command flows with permissions, roles and channels
  • discord.py mock events — fire any gateway event (member join, reaction add, voice state…) without a real server
  • discord.py mock Discord — a full in-memory Discord server your bot can't tell from the real thing
  • discord.py command testing — prefix commands, slash commands, context menus, autocomplete
  • discord.py interaction testing — buttons, selects, modals, ephemeral responses, deferred replies
  • discord.py bot testing without a token — no .env, no test guild, no rate limits

Comparison

SimCord dpytest Manual test server
No network / no token
Real discord.py internals Partial
Slash commands & components
Authentic error codes
Time control
Failure transcripts
Maintained for discord.py 2.x

Contributing

See CONTRIBUTING.md. Bug reports with a failing test are gold; if your bot hits an unimplemented route, the error names it — please open a parity gap issue.

License

MIT. Unofficial — not affiliated with Discord Inc. or the discord.py project.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

simcord-0.3.0.tar.gz (254.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

simcord-0.3.0-py3-none-any.whl (66.4 kB view details)

Uploaded Python 3

File details

Details for the file simcord-0.3.0.tar.gz.

File metadata

  • Download URL: simcord-0.3.0.tar.gz
  • Upload date:
  • Size: 254.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for simcord-0.3.0.tar.gz
Algorithm Hash digest
SHA256 f61d24ffc322e9a7964c280c0a9081d6a28e9e9c53dc4984a9d34a3819aa93c1
MD5 38cdaae4bb9f0d660d59ad5828064f65
BLAKE2b-256 f9f4f77d7effb7c600b521ff34a4cc32205185fd9b6fa84c77c27f8fe2fa3dc5

See more details on using hashes here.

Provenance

The following attestation bundles were made for simcord-0.3.0.tar.gz:

Publisher: release.yml on SilentHacks/simcord

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file simcord-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: simcord-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 66.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for simcord-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f4e7f9fa1d60257944a1de79dc5e233e9c19abdc2dcdfd9682d247dc6e36a49b
MD5 cfe0df497b9241f9b208b22c5afb1aaf
BLAKE2b-256 d84e2c6ad851486863e4903074e2598274394f58c0782b18625c63ad5d7add03

See more details on using hashes here.

Provenance

The following attestation bundles were made for simcord-0.3.0-py3-none-any.whl:

Publisher: release.yml on SilentHacks/simcord

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page