| Crates.io | teremock |
| lib.rs | teremock |
| version | 0.5.5 |
| created_at | 2026-01-21 11:32:02.928438+00 |
| updated_at | 2026-01-23 07:57:28.781572+00 |
| description | Fast integration testing library for teloxide Telegram bots |
| homepage | |
| repository | https://github.com/zerosixty/teremock |
| max_upload_size | |
| id | 2058944 |
| size | 393,704 |
Telegram · Realistic · Mocking — A fast, ergonomic testing library for teloxide bots.
teremock enables you to write fast, reliable integration tests for your Telegram bots without network access, API tokens, or external services. Run your entire test suite in seconds, not minutes.
use teremock::{MockBot, MockMessageText};
#[tokio::test]
async fn test_hello_command() {
let mut bot = MockBot::new(MockMessageText::new().text("/hello"), handler_tree()).await;
bot.dispatch().await;
let responses = bot.get_responses();
assert_eq!(responses.sent_messages.last().unwrap().text(), Some("Hello, World!"));
}
Tests run 15-30x faster than traditional approaches. The mock server starts once and persists across all dispatches within a test — no server restart overhead between interactions.
50 sequential dispatches: ~2 seconds (teremock)
50 sequential dispatches: ~30-60 seconds (server-per-dispatch)
Test your bot the way users experience it. Send messages, click buttons, trigger commands — then verify the responses. No internal state manipulation, no implementation coupling.
Test complete user flows without juggling multiple test functions. The update() method lets you simulate follow-up messages, button clicks, and entire conversations in a single test.
Works out of the box with #[tokio::test]. No custom thread builders, no special runtime configuration, no port management. Each MockBot gets its own server on a dynamically assigned port.
Access both the resulting message and the original bot request for detailed assertions:
let responses = bot.get_responses();
// Check the message that was sent
let msg = &responses.sent_messages_photo[0].message;
assert_eq!(msg.caption(), Some("Check out this image!"));
// Inspect the raw request your bot made
let request = &responses.sent_messages_photo[0].bot_request;
assert!(request.has_spoiler.unwrap_or(false));
MockMessageText, MockCallbackQuery, MockMessagePhoto, and moredptree::deps![]InMemStorage, RedisStorage, or any teloxide storageAdd to your Cargo.toml:
[dev-dependencies]
teremock = "0.5"
teremock tests your bot by running updates through your handler tree. Extract it into a separate function:
use teloxide::{
dispatching::{UpdateFilterExt, UpdateHandler},
prelude::*,
};
type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
// Your handler function
async fn hello_world(bot: Bot, message: Message) -> HandlerResult {
bot.send_message(message.chat.id, "Hello World!").await?;
Ok(())
}
// Extract the handler tree into a function
pub fn handler_tree() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> {
dptree::entry()
.branch(Update::filter_message().endpoint(hello_world))
}
// Use it in your main dispatcher
#[tokio::main]
async fn main() {
let bot = Bot::from_env();
Dispatcher::builder(bot, handler_tree())
.build()
.dispatch()
.await;
}
#[cfg(test)]
mod tests {
use teremock::{MockBot, MockMessageText};
use super::*;
#[tokio::test]
async fn test_hello_world() {
// Create a mock message
let mock_message = MockMessageText::new().text("Hi!");
// Create a bot with your handler tree
let mut bot = MockBot::new(mock_message, handler_tree()).await;
// Dispatch the update
bot.dispatch().await;
// Check the response
let responses = bot.get_responses();
let message = responses.sent_messages.last().expect("No messages sent");
assert_eq!(message.text(), Some("Hello World!"));
}
}
For more specific assertions, use typed response collections:
#[tokio::test]
async fn test_with_request_details() {
let mut bot = MockBot::new(MockMessageText::new().text("/photo"), handler_tree()).await;
bot.dispatch().await;
let responses = bot.get_responses();
// sent_messages_text gives you both the message AND the original request
let text_response = responses.sent_messages_text.last().unwrap();
assert_eq!(text_response.message.text(), Some("Here's your photo!"));
assert_eq!(text_response.bot_request.parse_mode, Some(ParseMode::Html));
}
Use update() to simulate follow-up messages in the same test. The mock server persists between dispatches, so you can test complete user flows:
#[tokio::test]
async fn test_conversation() {
let mut bot = MockBot::new(MockMessageText::new().text("/start"), handler_tree()).await;
// First message
bot.dispatch().await;
assert_eq!(
bot.get_responses().sent_messages.last().unwrap().text(),
Some("Welcome! Send me a number.")
);
// User sends a follow-up
bot.update(MockMessageText::new().text("42"));
bot.dispatch().await;
assert_eq!(
bot.get_responses().sent_messages.last().unwrap().text(),
Some("You sent: 42")
);
// Test callback queries too
bot.update(MockCallbackQuery::new().data("confirm"));
bot.dispatch().await;
assert_eq!(
bot.get_responses().sent_messages.last().unwrap().text(),
Some("Confirmed!")
);
}
If your bot uses teloxide's dialogue system for stateful conversations, teremock has you covered. Just inject your storage as a dependency and test away.
use teloxide::{
dispatching::{dialogue::InMemStorage, UpdateFilterExt, UpdateHandler},
dptree::deps,
prelude::*,
};
use teremock::{MockBot, MockMessageText};
#[derive(Clone, Default)]
pub enum State {
#[default]
Start,
AwaitingName,
AwaitingAge { name: String },
}
#[tokio::test]
async fn test_registration_flow() {
let mut bot = MockBot::new(MockMessageText::new().text("/start"), handler_tree()).await;
// Inject the storage — this is the key part for dialogues
bot.dependencies(deps![InMemStorage::<State>::new()]);
// Step 1: User sends /start
bot.dispatch().await;
assert_eq!(
bot.get_responses().sent_messages.last().unwrap().text(),
Some("Welcome! What's your name?")
);
// Step 2: User sends their name
bot.update(MockMessageText::new().text("Alice"));
bot.dispatch().await;
assert_eq!(
bot.get_responses().sent_messages.last().unwrap().text(),
Some("Nice to meet you, Alice! How old are you?")
);
// Step 3: User sends their age
bot.update(MockMessageText::new().text("25"));
bot.dispatch().await;
assert_eq!(
bot.get_responses().sent_messages.last().unwrap().text(),
Some("Registration complete! Alice, age 25.")
);
}
The dialogue state transitions happen naturally through your handler tree — no need to manually set states. This is black-box testing at its finest.
Messages
sendMessage, sendPhoto, sendVideo, sendAudio, sendVoicesendVideoNote, sendDocument, sendAnimation, sendStickersendLocation, sendVenue, sendContact, sendPoll, sendDicesendInvoice, sendMediaGroup, sendChatActionEditing
editMessageText, editMessageCaption, editMessageReplyMarkupManagement
deleteMessage, deleteMessages, forwardMessage, copyMessagepinChatMessage, unpinChatMessage, unpinAllChatMessagesUsers & Moderation
banChatMember, unbanChatMember, restrictChatMemberCallbacks & Commands
answerCallbackQuery, setMessageReaction, setMyCommandsFiles & Bot Info
getFile, getMe, getUpdates, getWebhookInfoThe examples/ directory contains complete bot implementations with tests:
| Example | Description |
|---|---|
| hello_world_bot | Simple message handling |
| calculator_bot | Dialogue state machine with callbacks |
| deep_linking_bot | Deep linking with command parameters |
| album_bot | Media group handling |
| file_download_bot | File upload and download operations |
| phrase_bot | Database integration patterns |
teremock works seamlessly with database-backed bots. Use your preferred test isolation strategy:
#[tokio::test]
async fn test_with_database() {
let pool = setup_test_database().await; // Your test DB setup
let mut bot = MockBot::new(MockMessageText::new().text("/save hello"), handler_tree()).await;
bot.dependencies(deps![pool.clone()]);
bot.dispatch().await;
// Verify bot response
assert_eq!(
bot.get_responses().sent_messages.last().unwrap().text(),
Some("Saved!")
);
// Verify database state
let saved = sqlx::query!("SELECT phrase FROM phrases")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(saved.phrase, "hello");
}
teremock builds upon the foundation laid by teloxide_tests by LasterAlex. The original library pioneered the concept of mock testing for teloxide bots.
Key architectural changes in teremock:
MockBot::new() for proper initializationMIT License — see LICENSE for details.