Skip to main content

Black-box testing for aiogram 3.x Telegram bots - no network, no token

Project description

teremok

PyPI Python CI

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


Download files

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

Source Distribution

teremok-0.2.0.tar.gz (77.3 kB view details)

Uploaded Source

Built Distribution

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

teremok-0.2.0-py3-none-any.whl (16.4 kB view details)

Uploaded Python 3

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

Hashes for teremok-0.2.0.tar.gz
Algorithm Hash digest
SHA256 0661d4738409bc8e4a52a830e9ee00e7f992a66957e64c7c94cb4a82199792a3
MD5 121d829642ab0ba613dc0e8b2c7baf2e
BLAKE2b-256 8a6aab5202966325839d267c2ebca639ce764505d16aba48037da81021f46ab5

See more details on using hashes here.

Provenance

The following attestation bundles were made for teremok-0.2.0.tar.gz:

Publisher: publish.yml on 0x216/teremok-python

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

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

Hashes for teremok-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 45565c9186abe945ec904025bcb6c58f6e5363ac520bfc8b939f0ff615c3bfee
MD5 526463cebabda02ddfb567c268cdfffb
BLAKE2b-256 33a82fcb632374655811ec9d99d7243809e8552c86d85f414ecfac8a1e13aef5

See more details on using hashes here.

Provenance

The following attestation bundles were made for teremok-0.2.0-py3-none-any.whl:

Publisher: publish.yml on 0x216/teremok-python

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