Lingo.Testing (Lingo v0.4.2)

Copy Markdown View Source

Testing a bot without Discord: interactions and events go through the bot's real callbacks, checks and collectors, and every API call it makes is recorded instead of sent.

defmodule MyBotTest do
  use ExUnit.Case, async: true

  import Lingo.Testing

  setup do
    start_bot(MyBot)
    :ok
  end

  test "ping" do
    run_command("ping")
    assert %{type: :channel_message, data: %{"content" => "Pong!"}} = await_response()
  end

  test "echo" do
    run_command("echo", options: [text: "hi"], guild_id: nil)
    assert %{data: %{"content" => "hi"}} = await_response()
  end
end

Each test's bot is isolated, so tests can run async. API calls are answered 200 with {} (interaction callbacks 204) unless :rest says otherwise.

Summary

Functions

Waits for the next API call the bot makes, and returns its :method, :path (without /api/v10), :query and decoded :body.

Waits for the bot to answer an interaction, and returns the answer: %{type: :channel_message, data: %{"content" => ...}} and so on. Raises if none comes within timeout ms.

Delivers a raw gateway dispatch, as Discord would send it: the cache is updated and the bot gets the parsed event. type is Discord's name, such as "MESSAGE_CREATE"; a name Lingo doesn't know raises, unless allow_unknown: true (the bot then gets it as :unknown). Options: :bot, :allow_unknown.

Runs autocomplete for a command, with the option being typed named by :focused. Takes the options of run_command/2.

Runs a command. Options: :options (keyword, by the declared option names; subcommands nest: [add: [item: "x"]]), :user_id (default "1"), :guild_id (default "2", nil for a DM), :channel_id, :permissions and :app_permissions (bitfields), :locale, :target_id with :type (:user or :message) for context menu commands, and :bot (the name start_bot/2 returned) when the test runs several bots.

Clicks a button, or picks :values in a select menu. Options: :values, :message_id, and those of run_command/2.

Delivers an event to the bot's Lingo.Bot.handle_event/2, as is. The payload is what Lingo.Event lists for the event

Starts an isolated bot for the calling test, and makes it the current bot. It's the bot's real supervision tree, run without a gateway connection and without registering commands. Options: :cache (as for the bot), and :rest, a function getting each API call's method, path and decoded body and returning {status, body}.

Submits a modal with text inputs keyed by custom id. Takes the options of run_command/2.

Functions

await_request(timeout \\ 1000)

@spec await_request(timeout()) :: map()

Waits for the next API call the bot makes, and returns its :method, :path (without /api/v10), :query and decoded :body.

await_response(timeout \\ 1000)

@spec await_response(timeout()) :: %{type: atom(), data: map() | nil}

Waits for the bot to answer an interaction, and returns the answer: %{type: :channel_message, data: %{"content" => ...}} and so on. Raises if none comes within timeout ms.

dispatch(type, data, opts \\ [])

@spec dispatch(String.t(), map(), keyword()) :: :ok

Delivers a raw gateway dispatch, as Discord would send it: the cache is updated and the bot gets the parsed event. type is Discord's name, such as "MESSAGE_CREATE"; a name Lingo doesn't know raises, unless allow_unknown: true (the bot then gets it as :unknown). Options: :bot, :allow_unknown.

run_autocomplete(name, opts)

@spec run_autocomplete(
  String.t(),
  keyword()
) :: :ok

Runs autocomplete for a command, with the option being typed named by :focused. Takes the options of run_command/2.

run_command(name, opts \\ [])

@spec run_command(
  String.t(),
  keyword()
) :: :ok

Runs a command. Options: :options (keyword, by the declared option names; subcommands nest: [add: [item: "x"]]), :user_id (default "1"), :guild_id (default "2", nil for a DM), :channel_id, :permissions and :app_permissions (bitfields), :locale, :target_id with :type (:user or :message) for context menu commands, and :bot (the name start_bot/2 returned) when the test runs several bots.

run_component(custom_id, opts \\ [])

@spec run_component(
  String.t(),
  keyword()
) :: :ok

Clicks a button, or picks :values in a select menu. Options: :values, :message_id, and those of run_command/2.

send_event(event, payload, opts \\ [])

@spec send_event(Lingo.Event.name(), Lingo.Event.payload(), keyword()) :: :ok

Delivers an event to the bot's Lingo.Bot.handle_event/2, as is. The payload is what Lingo.Event lists for the event:

send_event(:message_delete, %Lingo.Event.MessageDelete{id: "1", channel_id: "2"})

Options: :bot.

start_bot(bot, opts \\ [])

@spec start_bot(
  module(),
  keyword()
) :: atom()

Starts an isolated bot for the calling test, and makes it the current bot. It's the bot's real supervision tree, run without a gateway connection and without registering commands. Options: :cache (as for the bot), and :rest, a function getting each API call's method, path and decoded body and returning {status, body}.

Returns the bot's name, which the other functions take as :bot when a test starts several bots.

submit_modal(custom_id, values, opts \\ [])

@spec submit_modal(String.t(), %{required(String.t()) => String.t()}, keyword()) ::
  :ok

Submits a modal with text inputs keyed by custom id. Takes the options of run_command/2.