Skip to content

0x216/teremok-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

About

Black-box testing for aiogram 3.x Telegram bots - no network, no token. Python counterpart of Rust's teremock.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages