# `Ussd.Test`
[🔗](https://github.com/spesohq/ussd/blob/main/lib/ussd/test.ex#L1)

A fluent helper for exercising a USSD flow end-to-end in ExUnit tests, mirroring a
caller dialing in and replying screen by screen.

    import Ussd.Test

    test "buying airtime" do
      build(MyApp.States.Welcome)
      |> start()
      |> assert_see("Welcome")
      |> input("1")
      |> assert_see("Enter amount")
      |> input("5")
      |> assert_terminated()
    end

`assert_see/2` and `assert_terminated/1` work against the default test response
(a `{message, terminating?}` tuple) or against a configured `use_response/2` whose
result looks like `%{message: ..., terminating: ...}`. Anything else is matched
against its `inspect/1`'d form, which still substring-matches for `assert_see/2`
but leaves the terminating flag unknown - prefer the default response unless
you're specifically testing a `Ussd.Response` formatter.

Each `acting_as/2` name gets its own session id and its own remembered screen, so
switching between callers and back doesn't lose or mix up either one's state.

# `t`

```elixir
@type t() :: %Ussd.Test{
  actor: term(),
  actors: term(),
  bag: term(),
  configurators: term(),
  continuing_mode: term(),
  continuing_state: term(),
  continuing_ttl: term(),
  exception_handler: term(),
  initial_state: term(),
  response: term(),
  started?: term(),
  store: term()
}
```

# `acting_as`

```elixir
@spec acting_as(t(), String.t()) :: t()
```

Switches which simulated caller is dialing in. Assertions and `input/2` after this
apply to `actor`. The first time a given name is used it dials in fresh (like
`start/2`); switching back to a name used before restores exactly what that caller
last saw, with no re-dispatch.

# `additional`

```elixir
@spec additional(t(), map()) :: t()
```

Sets the extra `Ussd.Context` bag values every dispatch in this session sees.

# `assert_context_has`

```elixir
@spec assert_context_has(t(), term(), term() | (term() -&gt; boolean()) | nil) :: t()
```

Asserts `key` is present in the `Ussd.Context` bag from the last dispatch.
With no `expectation`, just asserts it's non-nil; pass a value to compare
equality, or a 1-arity function to assert a predicate against it.

# `assert_context_missing`

```elixir
@spec assert_context_missing(t(), term()) :: t()
```

Asserts `key` is absent (or `nil`) in the `Ussd.Context` bag from the last dispatch.

# `assert_not_terminated`

```elixir
@spec assert_not_terminated(t()) :: t()
```

Asserts the session is still awaiting input after the last dispatch.

# `assert_record_has`

```elixir
@spec assert_record_has(t(), String.t(), term() | (term() -&gt; boolean()) | nil) :: t()
```

Asserts `key` is set on the current actor's `Ussd.Record`. See `assert_context_has/3` for `expectation`.

# `assert_record_missing`

```elixir
@spec assert_record_missing(t(), String.t()) :: t()
```

Asserts `key` is unset on the current actor's `Ussd.Record`.

# `assert_see`

```elixir
@spec assert_see(t(), String.t()) :: t()
```

Asserts the current screen's message contains `expected`. Returns `test` unchanged so it chains.

# `assert_terminated`

```elixir
@spec assert_terminated(t()) :: t()
```

Asserts the session ended on the last dispatch.

# `build`

```elixir
@spec build(module()) :: t()
```

Starts configuring a test session for `initial_state`.

# `input`

```elixir
@spec input(t(), String.t()) :: t()
```

Replies with `input`, as if the caller typed it and pressed send.

# `start`

```elixir
@spec start(t(), String.t()) :: t()
```

Dials in for the first time, optionally with an initial `input` (e.g. a shortcode).

# `timeout`

```elixir
@spec timeout(t(), pos_integer(), String.t()) :: t()
```

Simulates the current actor redialing after a dropped call: sleeps for
`ttl_seconds` (plus a small buffer, so a `continuing_ttl` of a matching or shorter
duration actually expires), then dispatches `input` under a brand-new session id
for the same caller - the same thing a real "new dial, same phone number" looks
like to `Ussd.use_continuing_state/4`. Keep TTLs short in tests - this really
sleeps the test process.

# `use_configurator`

```elixir
@spec use_configurator(t(), module()) :: t()
```

Applies a `Ussd.Configurator` to every dispatch in this test session.

# `use_continuing_state`

```elixir
@spec use_continuing_state(
  t(),
  Ussd.ContinuingMode.t(),
  pos_integer() | nil,
  module() | nil
) :: t()
```

Configures resumable-session behavior for this test session - see `Ussd.use_continuing_state/4`.

# `use_exception_handler`

```elixir
@spec use_exception_handler(t(), module() | function()) :: t()
```

Overrides the exception handler under test.

# `use_response`

```elixir
@spec use_response(t(), module() | function()) :: t()
```

Overrides the response formatter under test, instead of the DSL's own default `{message, terminating?}` tuple.

# `use_store`

```elixir
@spec use_store(t(), module()) :: t()
```

Overrides which `Ussd.Cache` implementation this test session's `Ussd.Record` calls use.

---

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