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(*texts)

Returns True if this response text contains any of the given texts, False otherwise.

Example:

assert response.contains("Hello")

Response.matches(*regexes)

Returns True if this response text matches any of the given regexes, False otherwise. regexes may be a list of either strings or compiled patterns.

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(*texts)

Возвращает True, если текст ответа содержит хотя бы одну из указанных подстрок, иначе False.

Пример:

assert response.contains("Привет")

Response.matches(*regexes)

Возвращает 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.3.0.tar.gz (7.0 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.3.0-py3-none-any.whl (8.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: aiogram_bot_tester-0.3.0.tar.gz
  • Upload date:
  • Size: 7.0 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.3.0.tar.gz
Algorithm Hash digest
SHA256 50bc3cb2b4507cc45b8e776dba9fff59104c43ac92fe2fcbbabbbffaee764c35
MD5 504a00cc841dcc7646c3091ec53f8481
BLAKE2b-256 515d00b92a9fe9d1986af622a1a6d53d3c15b5d0ba800a65eef56c87c41534d5

See more details on using hashes here.

File details

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

File metadata

  • Download URL: aiogram_bot_tester-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 8.0 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.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9378a76da3f5b9482aef0a8addf8e1cd3e64fe73f8a452c3295c7ec5b23af03f
MD5 398807249f5453b70a8d96f26b292e31
BLAKE2b-256 3a9e2d361c174aa3f174f8ab71a6ac4738135e5d59f7f904e08c36b93fc02a4f

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