Skip to main content

Test your Telegram bots easily

Project description

aiogram_bot_tester

aiogram telegram status python PyPI Downloads

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-обновлений, перехвата вызовов bot API и удобного слоя для проверок.

Installation / Установка

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

pip install aiogram_bot_tester

Latest development version / Последняя версия из репозитория:

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

[!NOTE] aiogram_bot_tester also installs pytest-asyncio for async tests. The library itself does not depend on it directly; it is included for convenience.

aiogram_bot_tester также устанавливает pytest-asyncio для асинхронных тестов. Сама библиотека не зависит от него напрямую; пакет добавлен для удобства.

Quick example / Пример

import pytest
import aiogram
from aiogram import filters, types

from aiogram_bot_tester import BotTester

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:
    tester = BotTester.from_routers(router)

    # For simpler cases when you have only a `Dispatcher`:
    # tester = BotTester.from_dispatcher(dispatcher)

    # Send a "/start" command
    response = await tester.start()
    assert response.contains_text("Hello")

Public API / Публичный API

BotTester.from_routers(*routers, **kwargs)

Construct a BotTester instance from one or more aiogram.Router objects.

tester = BotTester.from_routers(router)

BotTester.from_dispatcher(dispatcher, **kwargs)

Construct a BotTester instance from an existing dispatcher.

tester = BotTester.from_dispatcher(dispatcher)

send_message(text, timeout=None)

Send a user message to the bot.

Returns a BotMessage. If the bot does not produce any response, NoBotResponseError is raised.

response = await tester.send_message("/start")
assert response.contains_text("Hello")

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

Send a command to the bot.

Each positional argument is converted to str and appended to the command.

response = await tester.send_command("sum", 1, 2)
assert response.contains_text("3")

start()

Shortcut for send_command("start").

response = await tester.start()

tap_button(label, timeout=None)

Tap a button attached to the last bot message.

Reply buttons and inline callback buttons are supported. URL buttons are not tappable; they can only be asserted with has_url_button().

If there is no last bot message, NoBotMessageError is raised. If the button does not exist, ButtonNotFoundError is raised. If several buttons with the same label exist, AmbiguousButtonError is raised. If the bot does not produce a response after the tap, NoBotResponseError is raised. If the target button is a URL button, UrlButtonInteractionError is raised.

response = await tester.tap_button("Next")

in_state(state)

Return True when the bot is currently in the given FSM state.

assert await tester.in_state(Form.name)
assert await tester.in_state(None)

data_has(**kwargs)

Return True when the FSM data contains all the provided key-value pairs.

assert await tester.data_has(name="Bob")

BotMessage assertions / Проверки BotMessage

Each call that returns a bot response gives you a BotMessage object.

contains_text(*texts)

Return True if the message text contains any of the provided substrings.

assert response.contains_text("Hello", "Hi")

search_regex(*patterns)

Return True if the message text matches any of the provided regular expressions.

Patterns may be strings or compiled re.Pattern objects.

assert response.search_regex(r"\d+")

has_button(label)

Return True if the message contains a button with the given label.

assert response.has_button("Yes")

has_callback_button(label, callback_data)

Return True if the message contains an inline callback button with the given label and callback data.

assert response.has_callback_button("Press", "press")

has_url_button(label, url)

Return True if the message contains a URL button with the given label and URL.

assert response.has_url_button("Google", "https://google.com")

has_keyboard(keyboard)

Return True if the message keyboard matches the provided matrix of button labels.

assert response.has_keyboard([
    ["Yes", "No"],
    ["Cancel"],
])

Errors / Ошибки

The public API uses specific exceptions:

  • NoBotResponseError — the bot did not answer.
  • NoBotMessageError — an operation requires a previous bot message.
  • ButtonNotFoundError — the requested button was not found.
  • AmbiguousButtonError — more than one button with the same label was found.
  • UrlButtonInteractionError — a URL button was tapped, but URL buttons cannot be interacted with in tests.

Публичный API использует специальные исключения:

  • NoBotResponseError — бот не ответил.
  • NoBotMessageError — операция требует предыдущего сообщения от бота.
  • ButtonNotFoundError — нужная кнопка не найдена.
  • AmbiguousButtonError — найдено несколько кнопок с одинаковой подписью.
  • UrlButtonInteractionError — была нажата URL-кнопка, но такие кнопки нельзя взаимодействовать в тестах.

Full example / Полный пример

import pytest
import aiogram
from aiogram import F
from aiogram.fsm import context, state
from aiogram.types import (
    CallbackQuery,
    InlineKeyboardButton,
    InlineKeyboardMarkup,
    KeyboardButton,
    Message,
    ReplyKeyboardMarkup,
)

from aiogram_bot_tester import BotTester


class Registration(state.StatesGroup):
    name = state.State()
    password = state.State()


router = aiogram.Router()


@router.message(aiogram.filters.CommandStart())
async def cmd_start(message: Message, state: context.FSMContext) -> None:
    await message.answer(
        "Hello! What's your name?",
        reply_markup=ReplyKeyboardMarkup(
            keyboard=[[KeyboardButton(text="Cancel")]],
            resize_keyboard=True,
        ),
    )
    await state.set_state(Registration.name)


@router.message(Registration.name, aiogram.F.text)
async def name_chosen(message: Message, state: context.FSMContext) -> None:
    name = message.text
    await message.answer(
        f"Hi, {name}! What about your password?",
        reply_markup=InlineKeyboardMarkup(
            inline_keyboard=[
                [InlineKeyboardButton(text="Continue", callback_data="continue")]
            ],
        ),
    )
    await state.update_data(name=name)
    await state.set_state(Registration.password)


@router.callback_query(F.data == "continue")
async def on_continue(callback: CallbackQuery) -> None:
    await callback.message.answer("Please send your password.")


@pytest.mark.asyncio
async def test_full_flow() -> None:
    tester = BotTester.from_routers(router)

    response = await tester.start()
    assert response.contains_text("Hello")
    assert response.has_button("Cancel")
    assert await tester.in_state(Registration.name)

    response = await tester.send_message("Bob")
    assert response.contains_text("Hi, Bob!")
    assert response.has_callback_button("Continue", "continue")
    assert await tester.data_has(name="Bob")
    assert await tester.in_state(Registration.password)

    response = await tester.tap_button("Continue")
    assert response.contains_text("Please send your password.")

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-1.0.0.tar.gz (8.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-1.0.0-py3-none-any.whl (9.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: aiogram_bot_tester-1.0.0.tar.gz
  • Upload date:
  • Size: 8.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-1.0.0.tar.gz
Algorithm Hash digest
SHA256 1b15c9a6ffcc7682112f83422fb9bb22c671f27d78635e5ec56b052e0622acc9
MD5 0675559d0974ddbc7d343daf6d80fb70
BLAKE2b-256 65bb5d3c97b456d6168f1fa91087e6742877c0306281dd41d08c7d2830c24449

See more details on using hashes here.

File details

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

File metadata

  • Download URL: aiogram_bot_tester-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 9.7 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-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0ba4680040ca156455a1cdd552679f247da852d95fce4484418fa027b3646aec
MD5 26d1aaa22ef071427d7cb97d0825a66c
BLAKE2b-256 e89afa7c50ac929d7a515daa3ee87a25682d8b3486bb602c935d9993c4a3580a

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