Test Bot Handlers
View SourceBot logic can be tested without a Telegram token or network connection. Build
an update struct, create a client with a fake Nadia.HTTPClient, and call the
handler directly.
The examples in Nadia's examples
directory are tested this way by the normal mix test suite.
Define A Fake HTTP Adapter
The adapter receives a public Nadia.HTTPRequest and returns a public
Nadia.HTTPResponse. A useful fake sends requests back to the test process so
assertions can inspect the outgoing Bot API call.
defmodule MyApp.FakeHTTPClient do
@behaviour Nadia.HTTPClient
@impl Nadia.HTTPClient
def post(%Nadia.HTTPRequest{} = request) do
send(self(), {:nadia_request, request})
{:ok,
%Nadia.HTTPResponse{
status_code: 200,
body: ~s({"ok":true,"result":{"message_id":2,"date":1700000001,"chat":{"id":123,"type":"private"},"text":"Ready"}})
}}
end
endReturn the response shape expected by the method under test. For example,
answerCallbackQuery returns a JSON true, while sendMessage returns a
message object.
Construct An Update
Keep fixture builders small and set only the fields the handler uses:
defp message_update(text) do
%Nadia.Model.Update{
update_id: 1,
message: %Nadia.Model.Message{
message_id: 1,
date: 1_700_000_000,
text: text,
from: %Nadia.Model.User{id: 456, first_name: "User"},
chat: %Nadia.Model.Chat{id: 123, type: "private"}
}
}
endThen invoke the same handler entry point used by polling or a webhook:
test "replies to start" do
update = message_update("/start")
client =
Nadia.Client.new(
token: "123:test-token",
http_client: MyApp.FakeHTTPClient
)
context = Nadia.Context.new(update, client)
assert {:ok, %Nadia.Model.Message{}} =
MyApp.Bot.handle_update(update, context)
assert_received {:nadia_request,
%Nadia.HTTPRequest{body: {:form, params}}}
assert {"chat_id", "123"} in params
assert {"text", "Ready"} in params
endUse a syntactically plausible test token; the fake adapter prevents it from leaving the process.
Test Raw Webhook Input
Use Nadia.Webhook.dispatch_body/3 when parsing and secret verification are
part of the behavior under test:
assert {:ok, %Nadia.Model.Message{}} =
Nadia.Webhook.dispatch_body(
Jason.encode!(%{
update_id: 1,
message: %{
message_id: 1,
date: 1_700_000_000,
text: "/start",
chat: %{id: 123, type: "private"}
}
}),
MyApp.Bot,
headers: [{"x-telegram-bot-api-secret-token", "test-secret"}],
secret_token: "test-secret",
client: client
)Add separate tests for invalid JSON, an invalid secret, callback updates, updates without a message, and Bot API error responses. Handler tests should assert both the returned value and the meaningful outgoing parameters.
For a new bot, mix nadia.gen.bot MyApp.Bot --polling generates this testing
pattern with the starter handler.