HTTP Adapter for testing with per-process isolation using NimbleOwnership.
Supports both private mode (per-process stubs for async tests) and global mode (shared stubs for synchronous tests).
This module implements the low-level adapter. In tests you typically interact
with it through ExGram.Test, which provides a cleaner interface and handles
process isolation automatically via start_bot/3.
Usage
# In test_helper.exs or application supervision tree
ExGram.Adapter.Test.start_link()
# In your test module - use ExGram.Test for the full setup
defmodule MyBotTest do
use ExUnit.Case, async: true
use ExGram.Test # sets up verify_on_exit! and set_from_context automatically
setup context do
{bot_name, _} = ExGram.Test.start_bot(context, MyApp.Bot)
{:ok, bot_name: bot_name}
end
test "my test", %{bot_name: bot_name} do
ExGram.Test.expect(:send_message, fn body ->
assert body[:text] == "value"
{:ok, %{message_id: 1}}
end)
ExGram.Test.push_update(bot_name, update)
# You can inspect calls after the fact
calls = ExGram.Test.get_calls()
# calls = [{:post, :send_message, %{chat_id: 123, ...}}]
end
endYou can learn more in the testing guide
API Methods
All stub and expect functions accept action atoms (:send_message) that match the names of the
ExGram functions (ExGram.get_me -> :get_me).
Summary
Functions
Allow another process to use this process's stubs.
Backward-compatible wrapper. The name parameter is ignored - isolation is now per-process.
Backward-compatible wrapper. The name parameter is ignored - isolation is now per-process.
Provide a child specification for supervising the test ownership server.
Backward-compatible wrapper. Cleans the current owner's state.
Expect a catch-all response with a callback that receives action atom and body. Consumed after the first call (n=1).
Expect a catch-all response with a callback that receives action atom and body. Consumed after n calls.
Expect a response for a path or action atom. Consumed after n calls, then removed. Expectations are checked before stubs.
Get all calls recorded for the current owner.
Backward-compatible wrapper. The name parameter is ignored.
Chooses the ExGram.Test mode based on context.
Switch to global mode (one owner serves all callers).
Switch to private mode (per-process isolation).
Starts the NimbleOwnership server used to track per-test ownership for ExGram.Adapter.Test.
Register a catch-all stub callback for the current owner to handle requests that have no path-specific stub.
Stub a response for a Telegram API path or action atom. Always returns this response. Owned by the calling process.
Stub an error for a Telegram API path or action atom. Always returns this error. Owned by the calling process.
Verify all expectations were consumed. Raises if any expectations remain.
Registers cleanup on test exit that verifies expectations were met.
Call this in your test's setup block via setup {ExGram.Test, :verify_on_exit!}.
Functions
Allow another process to use this process's stubs.
Backward-compatible wrapper. The name parameter is ignored - isolation is now per-process.
Backward-compatible wrapper. The name parameter is ignored - isolation is now per-process.
@spec child_spec(Keyword.t()) :: Supervisor.child_spec()
Provide a child specification for supervising the test ownership server.
Parameters
- opts: Keyword list of options forwarded to start_link/1.
The returned map is a child specification suitable for use in a supervision tree.
Backward-compatible wrapper. Cleans the current owner's state.
Expect a catch-all response with a callback that receives action atom and body. Consumed after the first call (n=1).
Example
expect(fn action, body ->
assert action == :send_message
{:ok, %ExGram.Model.Message{...}}
end)
Expect a catch-all response with a callback that receives action atom and body. Consumed after n calls.
Example
expect(2, fn action, body ->
{:ok, %ExGram.Model.Message{...}}
end)
Expect a response for a path or action atom. Consumed after n calls, then removed. Expectations are checked before stubs.
The response can be:
- A static value (will be wrapped in
{:ok, value}if not already a tuple) - An arity-1 function that receives the request body
Examples
# Static response with atom, default n=1
expect(:send_message, %ExGram.Model.Message{...})
# Static response with count
expect(:send_message, 2, %ExGram.Model.Message{...})
# Dynamic response based on body
expect(:send_message, fn body ->
assert body["text"] =~ "Welcome"
{:ok, %ExGram.Model.Message{...}}
end)
# Dynamic response with count
expect(:send_message, 2, fn body ->
{:ok, %ExGram.Model.Message{...}}
end)
Get all calls recorded for the current owner.
Backward-compatible wrapper. The name parameter is ignored.
@spec set_from_context(term()) :: :ok
Chooses the ExGram.Test mode based on context.
When async: true is used, set_private/1 is called,
otherwise set_global/1 is used.
Examples
setup {ExGram.Test, :set_from_context}
Switch to global mode (one owner serves all callers).
Examples
setup {ExGram.Test, :set_global}
Switch to private mode (per-process isolation).
Examples
setup {ExGram.Test, :set_private}
Starts the NimbleOwnership server used to track per-test ownership for ExGram.Adapter.Test.
Accepts the same options as NimbleOwnership.start_link/1. By default sets the :name option to the module's ownership server name so the server can be referenced globally; call this once (for example from test_helper.exs or your application supervision tree).
Register a catch-all stub callback for the current owner to handle requests that have no path-specific stub.
The provided callback will be invoked with the action (an atom) and the request body when no action-specific stub or expectation matches; its return value is used as the response (for example {:ok, value} or {:error, reason}).
Parameters
- callback: a function of arity 2 receiving
(action :: atom(), body :: any()).
Example
stub(fn action, body ->
case action do
:send_message -> {:ok, %ExGram.Model.Message{...}}
:send_chat_action -> {:ok, true}
end
end)
Stub a response for a Telegram API path or action atom. Always returns this response. Owned by the calling process.
The response can be:
- A static value (will be wrapped in
{:ok, value}if not already a tuple) - An arity-1 function that receives the request body
- An arity-2 function that receives path and body (use
stub/1instead for catch-all)
Examples
# Static response with atom
stub(:send_message, %ExGram.Model.Message{...})
# Dynamic response based on body
stub(:send_message, fn body ->
assert body["text"] =~ "Hello"
{:ok, %ExGram.Model.Message{...}}
end)
Stub an error for a Telegram API path or action atom. Always returns this error. Owned by the calling process.
Verify all expectations were consumed. Raises if any expectations remain.
Registers cleanup on test exit that verifies expectations were met.
Call this in your test's setup block via setup {ExGram.Test, :verify_on_exit!}.