# `Slink.Testing`
[🔗](https://github.com/wkirschbaum/slink/blob/v0.6.0/lib/slink/testing.ex#L1)

Unit-test your bot's `c:Slink.handle_event/2` without Slack.

Two pieces: `event/2` builds realistic event fixtures (through the same
normaliser the Socket Mode transport uses, so shapes can't drift from
production), and `run/3` executes your handler with every Slack call
captured instead of sent:

    defmodule MyBotTest do
      # run/3 swaps process-global test seams — keep these tests async: false.
      use ExUnit.Case, async: false
      import Slink.Testing

      test "greets a mention in its thread" do
        run = run(MyBot, event(:app_mention, text: "<@U1BOT> hi", thread_ts: "1.0"))

        assert [{"chat.postMessage", %{text: "hi" <> _, thread_ts: "1.0"}}] = run.calls
      end

      test "a slash command is answered ephemerally" do
        run = run(MyBot, event(:slash_command, command: "/deploy", text: "prod"))

        assert [%{response_type: "ephemeral"}] = run.responses
      end
    end

`run/3` is fully synchronous: the handler runs in the test process, sends
bypass the rate-limit workers, and by the time it returns, `run.calls` and
`run.responses` hold everything the bot did, in order. A handler that raises
bubbles into the test, where you want it.

Failure paths are testable by scripting the fake API with `:api`:

    run = run(MyBot, event(:app_mention), api: fn
      "chat.postMessage", _params -> {:error, "channel_not_found"}
      _method, _params -> {:ok, %{"ok" => true}}
    end)

# `context`

A handler context for `event`, as the dispatcher would build it.

Options: `:bot_token` (default `"xoxb-test"`), `:bot_user_id` (default
`nil` — set it to make `mentions_me?/1` live).

# `event`

Build a `Slink.Event` fixture of the given `type`.

The fixture is assembled as a Socket Mode envelope and normalised by
`Slink.Event.from_socket_mode/1` — the exact production path. Common
attributes (all optional, with sensible defaults): `:channel`, `:user`,
`:ts`, `:text`, `:thread_ts`, `:team_id`. Anything under `:extra` (a map of
string keys) is merged into the inner payload for shapes not covered here.

Supported types and their specific attributes:

  * `:app_mention`, `:message` — `:text`, `:thread_ts`, `:subtype`,
    `:event_id`. A `"message_changed"` / `"message_deleted"` subtype builds
    the nested shape Slack really sends (your attrs describe the nested
    message; the wrapper gets its own edit-event `ts`), so the accessors
    behave as they do in production.
  * `:reaction_added` / `:reaction_removed` — `:emoji` (default `"eyes"`)
  * `:app_home_opened` — `:user`
  * `:slash_command` (or `:slash_commands`) — `:command` (default
    `"/slink"`), `:text`, `:response_url`, `:trigger_id`
  * `:block_actions` — `:action_id` (default `"button"`), `:value`,
    `:message_ts`, `:thread_ts`, `:response_url`, `:trigger_id`
  * `:view_submission` — `:callback_id`, `:values` (the modal's
    `state.values` map), `:trigger_id`
  * `:shortcut`, `:message_action` — `:callback_id`, `:trigger_id`
  * `:assistant_thread_started`, `:assistant_thread_context_changed` —
    `:channel` (the assistant DM), `:thread_ts`, `:user`

# `run`

Run `module`'s handler for `event`, capturing every Slack call.

Returns a `Slink.Testing.Run` — the handler's `result` plus the `calls` and
`responses` it produced (including a `{:reply, …}` return value, which is
performed exactly as the dispatcher would — so for a `view_submission` it is
*dropped*, as production's sync-ack path drops it). Everything is
synchronous and in-process; nothing touches the network.

Options: `:bot_token` / `:bot_user_id` (see `context/2`), and `:api` — a
2-arity function `(method, params)` deciding what each Web API call returns
(default: success shapes), for exercising failure paths. Posts to a
`response_url` reach it as the pseudo-method `"response_url"`.

This works by swapping Slink's process-global test seams for the duration of
the call, so tests using `run/3` must be `async: false`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
