Black-box testing for aiogram 3.x Telegram bots - no network, no token
Project description
teremok
Black-box testing for aiogram 3.x Telegram bots. No network, no token, no mock servers - each test runs in milliseconds.
Inspired by Rust's teremock: send mock updates through your real dispatcher, then assert on what the bot sent back. Named after the folk tale about a little house whose guests arrive one by one - just like updates entering your bot.
Install
pip install teremok
Test setup
pip install teremok pytest-asyncio
teremok's tests (and the mock_bot fixture) are async def. Without
pytest-asyncio configured, async tests fail to run - add
this to your pyproject.toml (or the equivalent in pytest.ini):
[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
The quickstart below depends on this setting.
Quickstart
from aiogram import Router
from aiogram.filters import Command
from aiogram.types import Message
from teremok import MockBot, MockMessageText
router = Router()
@router.message(Command("start"))
async def cmd_start(message: Message) -> None:
await message.answer("Hello!")
async def test_start(mock_bot): # `mock_bot` fixture ships with the package
bot = mock_bot(router)
result = await bot.dispatch(MockMessageText("/start"))
assert result.handled
assert bot.requests.send_message[0].text == "Hello!"
Captures are typed aiogram objects, not JSON: bot.requests.send_message
returns SendMessage instances, bot.requests.send_photo[0].photo is the
actual InputFile your handler passed.
FSM and multi-step conversations
bot = mock_bot(form_router)
await bot.dispatch(MockMessageText("/form"))
await bot.dispatch(MockMessageText("Alice"))
await bot.dispatch(MockMessageText("30"))
assert bot.requests.send_message[-1].text == "Nice to meet you, Alice (30)!"
assert await bot.get_state() is None
# Or jump straight into the middle of a flow:
await bot.set_state(Form.age)
await bot.set_data({"name": "Bob"})
Files and media
bot.add_file("photo_1", b"...jpeg bytes...") # bot.download() returns this
await bot.dispatch(MockMessagePhoto(file_id="photo_1", caption="lunch"))
assert bot.requests.send_photo[0].photo.filename == "echo.jpg"
Custom API results and errors
from aiogram.methods import SendMessage
bot.add_result(SendMessage, ok=False, error_code=400,
description="Bad Request: chat not found")
# strict mode: MockBot(router, strict=True) fails on any un-queued call
Results are keyed by method type: queuing a SendMessage result never answers an
AnswerCallbackQuery (or any other method) call that happens to run first - every
other call keeps auto-responding.
Strict API rules (v0.2.0)
Every outgoing call is checked against documented Bot API rules before it
gets a response: text/caption length limits, HTML/MarkdownV2/legacy-Markdown
well-formedness, entity offset bounds, and inline keyboard button shape
(exactly one action field, callback_data ≤64 bytes). A violation raises a
genuine TelegramBadRequest - same description text, same check_response
route as a real rejection, nothing new to catch. Escape hatch for tests that
intentionally send malformed payloads: MockBot(router, validate=False).
Validation runs before queued results are consulted, so a call that fails
validation never consumes a queued result.
Full rule-by-rule reference, including what's deliberately not enforced, in
docs/validation.md.
What's covered
Every Bot API method is captured (interception happens below all methods, at aiogram's session seam). Auto-response fidelity per method is tracked honestly in docs/coverage.md; known edge cases live in docs/quirks.md.
CI's freshness gate regenerates that table against the latest aiogram release on every run, so a new aiogram release can turn CI red until someone regenerates and commits the table - that's expected behavior, not a teremok bug.
Examples
Four runnable bots with full test suites — the tests are the best
documentation of how to use teremok. Start with the
examples guide; the showcase is the
pizza order wizard (inline-keyboard toggles,
FSM, gettext i18n) and its 18 tests.
Every example also runs live: BOT_TOKEN=... python -m examples.pizza_order_bot.
Releasing (maintainers)
Tag vX.Y.Z and push - GitHub Actions builds and publishes via PyPI Trusted
Publishing (configure once in PyPI project settings).
Credits
The name and the whole idea are borrowed with love from teremock by @zerosixty — a Rust testing library for teloxide bots (MIT). teremok is its independent aiogram counterpart: no code is shared (different language, different framework), but the black-box testing philosophy — and the pun — are theirs. Related prior art: teloxide_tests.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file teremok-0.2.0.tar.gz.
File metadata
- Download URL: teremok-0.2.0.tar.gz
- Upload date:
- Size: 77.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0661d4738409bc8e4a52a830e9ee00e7f992a66957e64c7c94cb4a82199792a3
|
|
| MD5 |
121d829642ab0ba613dc0e8b2c7baf2e
|
|
| BLAKE2b-256 |
8a6aab5202966325839d267c2ebca639ce764505d16aba48037da81021f46ab5
|
Provenance
The following attestation bundles were made for teremok-0.2.0.tar.gz:
Publisher:
publish.yml on 0x216/teremok-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
teremok-0.2.0.tar.gz -
Subject digest:
0661d4738409bc8e4a52a830e9ee00e7f992a66957e64c7c94cb4a82199792a3 - Sigstore transparency entry: 2168400646
- Sigstore integration time:
-
Permalink:
0x216/teremok-python@6a545fb66d4309511e9424d6cc138bd3f34be827 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/0x216
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6a545fb66d4309511e9424d6cc138bd3f34be827 -
Trigger Event:
push
-
Statement type:
File details
Details for the file teremok-0.2.0-py3-none-any.whl.
File metadata
- Download URL: teremok-0.2.0-py3-none-any.whl
- Upload date:
- Size: 16.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
45565c9186abe945ec904025bcb6c58f6e5363ac520bfc8b939f0ff615c3bfee
|
|
| MD5 |
526463cebabda02ddfb567c268cdfffb
|
|
| BLAKE2b-256 |
33a82fcb632374655811ec9d99d7243809e8552c86d85f414ecfac8a1e13aef5
|
Provenance
The following attestation bundles were made for teremok-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on 0x216/teremok-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
teremok-0.2.0-py3-none-any.whl -
Subject digest:
45565c9186abe945ec904025bcb6c58f6e5363ac520bfc8b939f0ff615c3bfee - Sigstore transparency entry: 2168400661
- Sigstore integration time:
-
Permalink:
0x216/teremok-python@6a545fb66d4309511e9424d6cc138bd3f34be827 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/0x216
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6a545fb66d4309511e9424d6cc138bd3f34be827 -
Trigger Event:
push
-
Statement type: