Test your Telegram bots easily
Project description
aiogram_bot_tester
Navigation / Навигация - English version - Русская версия
A lightweight testing utility for offline testing of aiogram bots without real Telegram API calls.
Its goal is to make it easy to test bot logic deterministically by simulating Telegram updates, intercepting bot API calls, and exposing a clean assertion-friendly response layer.
English Version
Installation
With pip:
pip install git+https://github.com/samedit66/aiogram-bot-tester.git
With poetry:
poetry add git+https://github.com/samedit66/aiogram-bot-tester.git
With uv:
uv add git+https://github.com/samedit66/aiogram-bot-tester.git
What is this?
aiogram_bot_tester is a testing framework for bots built with aiogram.
Instead of running a real Telegram bot, it:
- simulates incoming Telegram updates (
Message,CallbackQuery) - intercepts outgoing bot API calls (
send_message, etc.) - captures state changes (
FSM) - provides a clean assertion API
Goal
Enable fast, deterministic, offline testing of Telegram bots without network, tokens, or Telegram API dependency.
Quick example
from aiogram_bot_tester import BotTester
tester = BotTester.from_routers(router)
response = await tester.send_message("/start")
assert response.text == "Hello"
assert response.has_inline_button("Press")
Core API
BotTester.from_routers(*routers)
Construct a BotTester instance from a sequence of Routerss.
tester = BotTester.from_routers(router)
send_message(text, **kwargs)
Send a message to the bot. Returns a special Response object described below.
response = await tester.send_message("/start")
assert response.text == "Hello"
click_reply_button(text, **kwargs)
Simulates clicking a reply button. Actually, it's just another way of calling send_message. Exists just to clarify intention.
response = await tester.click_reply_button("Option A")
click_inline_button(label)
Simulates clicking an inline button.
response = await tester.click_inline_button("Press")
Response object
Each interaction with a BotTester returns a Response object.
@dataclass
class Response:
text: str | None
state: State | None
message: object | None
Response.text
Last bot message text.
Response.state
FSM state.
Example:
class Form(StatesGroup):
name = State()
surname = State()
age = State()
# Inside a test:
response = await tester.send_message("/start")
assert response.state == Form.name # in_state() method does the same
Response.contains(substring)
Returns True if this response text contains the given substring, False otherwise.
Example:
assert response.contains("Hello")
Response.matches(regex)
Returns True if this response text matches the given regex, False otherwise.
Example:
assert response.matches("\d+")
Response.has_inline_button(label)
Returns True if this response has an inline button with label, False otherwise.
Example:
assert response.has_inline_button("Click me!")
Response.has_reply_button(label)
Returns True if this response has a button with label, False otherwise.
Example:
assert response.has_reply_button("Click me!")
Response.has_inline_keyboard_like(keyboard)
Returns True if this response has the specified inline keyboard, False otherwise.
Example:
assert response.has_inline_keyboard_like([
["Yes", "No"],
["Cancel"],
])
Response.has_reply_keyboard_like(keyboard)
Returns True if this response has the specified reply keyboard, False otherwise.
Example:
assert response.has_reply_keyboard_like([
["1", "2", "3"],
["4", "5", "6"],
])
Response.in_state(state)
Returns True if after this response the state is state, False otherwise.
Example:
assert response.in_state(Form.name)
🚀 Full example
import pytest
from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram_bot_tester import BotTester
router = Router()
@router.message()
async def echo(message: Message):
await message.answer(
f"Echo: {message.text}",
reply_markup=InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="Press me", callback_data="press")]
]
),
)
@router.callback_query(F.data == "press")
async def on_press(callback: CallbackQuery):
await callback.message.answer("Button clicked!")
@pytest.mark.asyncio
async def test_full_flow():
tester = BotTester.from_routers(router)
response = await tester.send_message("Hello")
assert response.text == "Echo: Hello"
response = await tester.click_inline_button("Press me")
assert response.text == "Button clicked!"
Русская версия
Установка
Через pip:
pip install git+https://github.com/samedit66/aiogram-bot-tester.git
Через poetry:
poetry add git+https://github.com/samedit66/aiogram-bot-tester.git
Через uv:
uv add git+https://github.com/samedit66/aiogram-bot-tester.git
Что это?
aiogram_bot_tester это тестировочный фреймворк для Telegram-ботов, написанных на aiogram.
Он:
- симулирует отправку различных Telegram-апдейтов (
Message,CallbackQuery) - предоставляет знакомый интерфейс для работы с ботом (
send_messageи т.д.) - хранит информацию о состоянии (
FSM) - предоставляет чистый и красивый API
Цель
Предоставить быстрое, детерменированное и оффлайн тестирование функционала Telegram-бота.
Пример
from aiogram_bot_tester import BotTester
tester = BotTester.from_routers(router)
response = await tester.send_message("/start")
assert response.text == "Привет"
assert response.has_inline_button("Нажми меня!")
Core API
BotTester.from_routers(*routers)
Создает объект BotTester из набора роутеров.
tester = BotTester.from_routers(router1, router2, router3)
send_message(text, **kwargs)
Симулирует отправку сообщения боту. Возвращает специальный объект Response, рассматриваемый далее.
response = await tester.send_message("/start")
assert response.text == "Привет"
click_reply_button(text, **kwargs)
Симулирует нажатие на reply-кнопку. На самом деле, это просто синоним для метода send_message, но с названием отражающим суть.
response = await tester.click_reply_button("Вариант А")
click_inline_button(label)
Симулирует нажатие на inline-кнопку.
response = await tester.click_inline_button("Нажми")
Response object
В результате любого взаимодействия с BotTester, возвращается объект-ответ Response.s
@dataclass
class Response:
text: str | None
state: State | None
message: object | None
Response.text
Последнее сообщение бота.
Response.state
Состояние.
Пример:
class Form(StatesGroup):
name = State()
surname = State()
age = State()
# Внутри теста:
response = await tester.send_message("/start")
assert response.state == Form.name # in_state() делает аналогичное действие
Response.contains(substring)
Возвращает True, если текст ответа содержит указанную подстроку, иначе False.
Пример:
assert response.contains("Привет")
Response.matches(regex)
Возвращает True, если текст ответа соответствует регулярному выражению, иначе False.
Пример:
assert response.matches("\d+")
Response.has_inline_button(label)
Возвращает True, если в ответе есть inline-кнопка с указанным текстом.
Привет:
assert response.has_inline_button("Нажми меня!")
Response.has_reply_button(label)
Возвращает True, если в ответе есть reply-кнопка с указанным текстом.
Example:
assert response.has_reply_button("Нажми меня!")
Response.has_inline_keyboard_like(keyboard)
Возвращает True, если inline-клавиатура совпадает с заданной структурой.
Example:
assert response.has_inline_keyboard_like([
["Да", "Нет"],
["Отмена"],
])
Response.has_reply_keyboard_like(keyboard)
Возвращает True, если reply-клавиатура совпадает с заданной структурой.
Example:
assert response.has_reply_keyboard_like([
["1", "2", "3"],
["4", "5", "6"],
])
Response.in_state(state)
Возвращает True, если бот находится в заданном состоянии.
Пример:
assert response.in_state(Form.name)
🚀 Полный пример теста
import pytest
from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram_bot_tester import BotTester
router = Router()
@router.message()
async def echo(message: Message):
await message.answer(
f"Echo: {message.text}",
reply_markup=InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="Нажми меня!", callback_data="press")]
]
),
)
@router.callback_query(F.data == "press")
async def on_press(callback: CallbackQuery):
await callback.message.answer("Кнопка нажата!")
@pytest.mark.asyncio
async def test_full_flow():
tester = BotTester.from_routers(router)
response = await tester.send_message("Hello")
assert response.text == "Echo: Hello"
response = await tester.click_inline_button("Нажми меня!")
assert response.text == "Кнопка нажата!"
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 aiogram_bot_tester-0.1.0.tar.gz.
File metadata
- Download URL: aiogram_bot_tester-0.1.0.tar.gz
- Upload date:
- Size: 6.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7c61f2eb554b1ac28e5c041f4e9796853b51dd39edc74aae8977e0557053706f
|
|
| MD5 |
116436514b39f615a0eeacaf694d2ec8
|
|
| BLAKE2b-256 |
46dcf9bf2a7ac7436ab652ed6040ae3f5b0380f186ebbf3e5c4a1cde18bde8b8
|
File details
Details for the file aiogram_bot_tester-0.1.0-py3-none-any.whl.
File metadata
- Download URL: aiogram_bot_tester-0.1.0-py3-none-any.whl
- Upload date:
- Size: 7.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3dab3d2e649315db8d89f93b29358d3f35f2183c9d0ca014031c42321b98a910
|
|
| MD5 |
6fc4a928bf5a235e3b8012a15abb6e37
|
|
| BLAKE2b-256 |
4c2e8514f0d32ee025b2c623ac9a7566124dcd29f3ce6f726c101efdca7926aa
|