Skip to main content

Test your Telegram bots easily

Project description

aiogram_bot_tester

aiogram telegram status python

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


Download files

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

Source Distribution

aiogram_bot_tester-0.1.0.tar.gz (6.1 kB view details)

Uploaded Source

Built Distribution

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

aiogram_bot_tester-0.1.0-py3-none-any.whl (7.1 kB view details)

Uploaded Python 3

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

Hashes for aiogram_bot_tester-0.1.0.tar.gz
Algorithm Hash digest
SHA256 7c61f2eb554b1ac28e5c041f4e9796853b51dd39edc74aae8977e0557053706f
MD5 116436514b39f615a0eeacaf694d2ec8
BLAKE2b-256 46dcf9bf2a7ac7436ab652ed6040ae3f5b0380f186ebbf3e5c4a1cde18bde8b8

See more details on using hashes here.

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

Hashes for aiogram_bot_tester-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3dab3d2e649315db8d89f93b29358d3f35f2183c9d0ca014031c42321b98a910
MD5 6fc4a928bf5a235e3b8012a15abb6e37
BLAKE2b-256 4c2e8514f0d32ee025b2c623ac9a7566124dcd29f3ce6f726c101efdca7926aa

See more details on using hashes here.

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