Ussd.Test (Ussd v0.2.0)

Copy Markdown View Source

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.

Summary

Functions

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.

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

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.

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

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

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

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

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

Asserts the session ended on the last dispatch.

Starts configuring a test session for initial_state.

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

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

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.

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

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

Overrides the exception handler under test.

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

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

Types

t()

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

Functions

acting_as(test, actor)

@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(test, bag)

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

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

assert_context_has(test, key, expectation \\ nil)

@spec assert_context_has(t(), term(), term() | (term() -> 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(test, key)

@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(test)

@spec assert_not_terminated(t()) :: t()

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

assert_record_has(test, key, expectation \\ nil)

@spec assert_record_has(t(), String.t(), term() | (term() -> boolean()) | nil) :: t()

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

assert_record_missing(test, key)

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

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

assert_see(test, expected)

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

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

assert_terminated(test)

@spec assert_terminated(t()) :: t()

Asserts the session ended on the last dispatch.

build(initial_state)

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

Starts configuring a test session for initial_state.

input(test, input)

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

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

start(test, input \\ "")

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

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

timeout(test, ttl_seconds, input \\ "")

@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(test, configurator)

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

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

use_continuing_state(test, mode, ttl \\ nil, continuing_state \\ nil)

@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(test, handler)

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

Overrides the exception handler under test.

use_response(test, response)

@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(test, store)

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

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