ExGram.Adapter.Test (ex_gram v0.65.0)

Copy Markdown View Source

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
end

You 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(owner_pid, allowed_pid)

Allow another process to use this process's stubs.

backdoor_error(path, error, name \\ nil)

Backward-compatible wrapper. The name parameter is ignored - isolation is now per-process.

backdoor_request(path, response, name \\ nil)

Backward-compatible wrapper. The name parameter is ignored - isolation is now per-process.

child_spec(opts)

@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.

clean(name \\ nil)

Backward-compatible wrapper. Cleans the current owner's state.

consume_expect_expectation(owner_pid, meta, action, body)

expect(callback)

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(n, callback)

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(action, n \\ 1, response)

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_calls()

Get all calls recorded for the current owner.

get_calls(name)

Backward-compatible wrapper. The name parameter is ignored.

set_from_context(context)

@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}

set_global(context \\ %{})

Switch to global mode (one owner serves all callers).

Examples

setup {ExGram.Test, :set_global}

set_private(context \\ %{})

Switch to private mode (per-process isolation).

Examples

setup {ExGram.Test, :set_private}

start_link(opts \\ [])

@spec start_link(Keyword.t()) :: {:ok, pid()} | {:error, term()}

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).

stub(callback)

@spec stub((atom(), any() -> any())) :: :ok

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(action, response)

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/1 instead 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_error(action, error)

Stub an error for a Telegram API path or action atom. Always returns this error. Owned by the calling process.

verify!(pid \\ self())

Verify all expectations were consumed. Raises if any expectations remain.

verify_on_exit!(context)

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!}.