Fixtures & configuration¶
SimCord works as a pytest plugin out of the box, and as a plain async context manager when you want explicit control. This guide covers both, plus the configuration options.
The pytest plugin¶
Installing the pytest extra registers the plugin automatically — no
conftest activation needed. It provides two fixtures.
simcord_bot — you define this¶
You tell SimCord how to build your bot by defining a simcord_bot fixture that returns a
fresh, unstarted bot:
# conftest.py
import pytest
from mybot import create_bot
@pytest.fixture
def simcord_bot():
return create_bot()
A fresh bot per test keeps tests isolated. If you forget to define it, SimCord raises a
clear UsageError telling you to.
simcord_env — you use this¶
The simcord_env fixture builds your simcord_bot, attaches it to a fresh virtual Discord,
runs the real login flow (including your setup_hook), drives it to READY, and hands
you the Env:
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")
assert channel.last_message.content == "Pong!"
At teardown it shuts the environment down cleanly and — unless you inspected them — re-raises any errors the bot swallowed.
Failing tests get a transcript for free
When a test fails, the plugin automatically attaches a transcript of every gateway event injected and REST call the bot made, in order, to the pytest report — so you can see exactly what the bot did without adding any logging.
Async test setup¶
The pytest extra installs pytest-asyncio. Set asyncio_mode = "auto" so async def
tests run without a per-test decorator:
Without pytest¶
Don't use pytest, or want a second environment in one test? Use simcord.run directly — an
async context manager that yields the same Env:
import simcord
async def main():
bot = create_bot()
async with simcord.run(bot) as env:
guild = env.create_guild()
channel = guild.create_text_channel("general")
alice = guild.add_member(env.create_user("alice"))
await alice.send(channel, "!ping")
assert channel.last_message.content == "Pong!"
Import style
The package can be imported directly (import simcord) so calls read
as simcord.run(...). Use whichever style you prefer — it's the same module.
Configuration options¶
Both simcord.run(bot, **options) and the underlying Env accept these keyword options:
| Option | Default | Effect |
|---|---|---|
strict_sync |
True |
Unsynced app commands can't be invoked — invoking one fails the test, catching forgotten tree.sync() calls. Set False to auto-register unsynced commands for isolated unit tests. |
check_errors |
True |
At teardown, errors the bot raised but the test never inspected are re-raised as an ExceptionGroup, so bot bugs can't pass silently. Set False to opt out. |
# An isolated unit test that doesn't care about sync, and inspects errors itself:
async with simcord.run(bot, strict_sync=False) as env:
...
Bot syncs to a hardcoded guild id?
Some bots pin tree.sync(guild=discord.Object(id=...)) to a specific guild id. Pass that
same id to create_guild so the synced commands land in a guild that exists, and
strict_sync can stay on:
Overriding options per test¶
Mark a test with @pytest.mark.simcord(...) and its keyword arguments are forwarded to
simcord.run, so a single test can change its environment without a custom fixture:
import pytest
@pytest.mark.simcord(strict_sync=False)
async def test_command_logic_in_isolation(simcord_env):
# this env auto-registers unsynced commands; other tests stay strict
...
For an override shared by many tests, drive simcord.run yourself in a small wrapper fixture
instead:
import pytest_asyncio
import simcord
@pytest_asyncio.fixture
async def lenient_env(simcord_bot):
async with simcord.run(simcord_bot, strict_sync=False) as env:
yield env
Next¶
- Errors & diagnostics —
env.errors,raise_errors(), the transcript and the HTTP log. - Core concepts — what the
Envgives you. - API reference — the full
Envandrunsignatures.