Testing¶
Dependency-free helpers for driving a MenuRouter and asserting menu
properties in unit tests, with no live Telegram bot. See the
Testing guide for usage.
This submodule is intentionally not exported from the package's eager __all__;
import it explicitly with import telegram_menu_builder.testing.
telegram_menu_builder.testing ¶
Testing helpers for driving menus and routers without a live Telegram bot.
This module provides small, dependency-free utilities (stdlib :mod:unittest.mock
only) for exercising a :class:~telegram_menu_builder.router.MenuRouter and
asserting properties of built menus in unit tests:
- :func:
simulate_tapfabricates a callbackUpdatefrom mocks and routes it. - :func:
tapis a convenience that encodes a handler + params, then simulates the tap. - :func:
assert_inlineverifies every button of a menu is an inline callback ref (no storage spill), accepting a :class:~telegram_menu_builder.builder.MenuBuilder, a :class:telegram.InlineKeyboardMarkup, or a raw Bot APIdict.
It is a submodule and is intentionally not exported from the package's eager
__all__; import it explicitly with import telegram_menu_builder.testing.
TapResult
dataclass
¶
Outcome of simulating a callback-query tap against a router.
Attributes:
| Name | Type | Description |
|---|---|---|
answered |
bool
|
Whether the router answered the callback query ( |
answer_text |
str | None
|
The text passed to |
edited_text |
str | None
|
The text a handler edited the message to via
|
handler_error |
Exception | None
|
The exception raised during routing, if any, captured via
the router's error middleware ( |
Source code in src/telegram_menu_builder/testing.py
simulate_tap
async
¶
simulate_tap(router: MenuRouter, callback_data: str, *, user_id: int = 1, context: Any | None = None) -> TapResult
Simulate a user tapping a button and route it through router.
A mock :class:telegram.Update is fabricated whose callback_query carries
callback_data and whose answer/edit_message_text are
:class:~unittest.mock.AsyncMock spies, and whose effective_user.id is
user_id. The update is passed to :meth:MenuRouter.route and a
:class:TapResult is derived from the spies. Nothing here requires a live bot.
Any exception raised while routing is captured via a temporary error handler
registered on the router and surfaced as :attr:TapResult.handler_error (the
router itself swallows handler exceptions, so this is how it is observed).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
router
|
MenuRouter
|
The router under test. |
required |
callback_data
|
str
|
The encoded callback data to deliver (as Telegram would). |
required |
user_id
|
int
|
The id exposed on |
1
|
context
|
Any | None
|
An optional bot context; a :class: |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
TapResult
|
class: |
TapResult
|
text, any edited message text, and any handler error. |
Source code in src/telegram_menu_builder/testing.py
tap
async
¶
Encode a handler + params and simulate tapping the resulting button.
This is a convenience over :func:simulate_tap: it builds a
:class:~telegram_menu_builder.types.MenuAction from handler/params,
encodes it with the router's encoder (spilling to storage if needed, exactly as
a real menu would), then routes the encoded callback data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
router
|
MenuRouter
|
The router under test. |
required |
handler
|
str
|
The handler name to invoke. |
required |
**params
|
Any
|
Parameters to encode into the callback data. |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
TapResult
|
class: |
Source code in src/telegram_menu_builder/testing.py
assert_inline ¶
Assert every button of target is an inline callback ref (no storage spill).
A button passes when it carries callback_data that is at most 64 bytes and
is prefixed I: or IC: (an inline payload). URL buttons and storage refs
(S:/P:) fail: this helper exists to prove a menu is fully self-contained
and needs no storage backend at runtime.
The target may be any of:
- a :class:
~telegram_menu_builder.builder.MenuBuilder— its own :meth:~telegram_menu_builder.builder.MenuBuilder.assert_inlinepre-flight is run and then its synchronous, storage-free :meth:~telegram_menu_builder.builder.MenuBuilder.to_markupis inspected; - a :class:
telegram.InlineKeyboardMarkup— itsinline_keyboardis inspected; - a raw Bot API
dict— its"inline_keyboard"list is inspected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
MenuBuilder | InlineKeyboardMarkup | dict[str, Any]
|
The menu to verify. |
required |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If any button is missing inline callback data, exceeds the 64-byte limit, or carries a storage reference instead of an inline payload. |
TypeError
|
If |