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.

Легковесная библиотека для оффлайн тестирования ботов, написанных на aiogram без использования интернета и Telegram API.

Цель данного пакета состоит в том, чтобы обеспечить проверку логики работы бота при помощи симуляции реальных Telegram-запросов с использованием чистых и удобных абстракций.

Installation / Установка

Stable version / Стабильная версия:

pip install aiogram_bot_tester

Latest development version / Последняя разрабатываемая версия

pip install git+https://github.com/samedit66/aiogram-bot-tester.git

Quick example / Пример

import aiogram
from aiogram import filters, types
from aiogram_bot_tester import BotTester
import pytest

router = aiogram.Router()

@router.message(filters.CommandStart())
async def cmd_start(message: types.Message) -> None:
    await message.answer("Hello! What's your name?")

@pytest.mark.asyncio
async def test_it() -> None:
    # Make a tester
    tester = BotTester.from_routers(router)

    # If you have a `Bot` instance with a `Dispatcher`, use the following:
    # tester = BotTester(bot=bot, dispatcher=dispatcher)

    # Send a command
    response = await tester.send_message("/start")

    # Check that ``"Hello"`` is in response
    assert response.contains("Hello")

English Version

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"

send_command(command, *command_args, prefix="/")

Helper to make sending commands more easily.

Instead of

await tester.send_message(f"/sum {a} {b}")

you can simply write

await tester.send_command("sum", a, b)

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!"

Русская версия


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 == "Привет"

send_command(command, *command_args, prefix="/")

Вспомогательная функция для более легкой отправки команд.

Вместо

await tester.send_message(f"/sum {a} {b}")

можно просто писать

await tester.send_command("sum", a, b)

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.2.0.tar.gz (6.5 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.2.0-py3-none-any.whl (7.5 kB view details)

Uploaded Python 3

File details

Details for the file aiogram_bot_tester-0.2.0.tar.gz.

File metadata

  • Download URL: aiogram_bot_tester-0.2.0.tar.gz
  • Upload date:
  • Size: 6.5 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.2.0.tar.gz
Algorithm Hash digest
SHA256 1dddb5887e6a68bfa8df8d1bb3c0b0c5867ff0b939f5bc7b8475ca89c415de20
MD5 6afe22f2fc30c3bb297eaf59595c5865
BLAKE2b-256 7ef37d0d6d7323e1e034dd888f45df4ceea3d44ac9cdc58a9e8907facb31d38c

See more details on using hashes here.

File details

Details for the file aiogram_bot_tester-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: aiogram_bot_tester-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 7.5 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.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fb6242d0c7ac4abbc248c58df3c057d5ef6ed162be9c4fe1482b71f9df8b19ca
MD5 4993d1ecfe99b2bf61d5c707f86eaff6
BLAKE2b-256 528d76d40e151255ec3c3543d1fb09c65f02cbef901d21dfbe2672b8e0de832d

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