DoubleDown.Testing (double_down v0.64.1)

Copy Markdown View Source

Test helpers for DoubleDown contracts.

Start the ownership server in test/test_helper.exs:

DoubleDown.Testing.start()

Then in your tests, register handlers per-contract:

setup do
  DoubleDown.Testing.set_module_handler(MyApp.Todos, MyApp.Todos.InMemory)
  :ok
end

Handlers are process-scoped via NimbleOwnership, so async: true tests are isolated. Use allow/3 to share handlers with child processes.

Summary

Functions

Allow a child process to use the current process's handlers.

Enable dispatch logging for a contract.

Retrieve the dispatch log for a contract.

Reset all handlers and logs for a process.

Register a module as the handler for a contract.

Register a stateful handler for a contract.

Register a function as the handler for a contract.

Start the DoubleDown ownership server.

Functions

allow(contract, owner_pid \\ self(), child_pid)

@spec allow(module(), pid(), pid() | (-> pid() | [pid()])) :: :ok | {:error, term()}

Allow a child process to use the current process's handlers.

Use this when spawning Tasks or other processes that need to dispatch through the same test handlers.

enable_log(contract)

@spec enable_log(module()) :: :ok

Enable dispatch logging for a contract.

After enabling, all dispatches through the contract's facade will be recorded. Retrieve with get_log/1.

get_log(contract)

@spec get_log(module()) :: [{module(), atom(), [term()], term()}]

Retrieve the dispatch log for a contract.

Returns a list of {contract, operation, args, result} tuples in the order they were dispatched.

reset(pid \\ self())

@spec reset(pid()) :: :ok

Reset all handlers and logs for a process.

Clears all NimbleOwnership entries owned by pid. Defaults to self().

on_exit caveat: reset() (without arguments) uses self(), which inside an on_exit callback is the callback process — not the test process. To reset the test process's handlers from on_exit, capture the pid first:

setup do
  pid = self()
  on_exit(fn -> DoubleDown.Testing.reset(pid) end)
  :ok
end

In most cases you don't need explicit cleanup — NimbleOwnership automatically cleans up when the owning process exits.

set_module_handler(contract, impl)

@spec set_module_handler(module(), module()) :: :ok

Register a module as the handler for a contract.

The module must implement the contract's @behaviour.

set_stateful_handler(contract, fun, initial_state)

@spec set_stateful_handler(module(), DoubleDown.Dispatch.Types.stateful_fun(), term()) ::
  :ok

Register a stateful handler for a contract.

The function may be 4-arity or 5-arity:

  • 4-arity: fn contract, operation, args, state -> {result, new_state} end
  • 5-arity: fn contract, operation, args, state, all_states -> {result, new_state} end

5-arity handlers receive a read-only snapshot of all contract states as the 5th argument. This enables cross-contract state access (e.g. a Queries handler reading the Repo InMemory store). The all_states map is keyed by contract module and includes a DoubleDown.Contract.GlobalState sentinel key. The handler must return only its own contract's new state — not the global map.

State is stored in NimbleOwnership and updated atomically on each dispatch.

set_stateless_handler(contract, fun)

@spec set_stateless_handler(module(), (module(), atom(), [term()] -> term())) :: :ok

Register a function as the handler for a contract.

The function receives (contract, operation, args) and returns the result.

start()

@spec start() :: {:ok, pid()} | {:error, term()}

Start the DoubleDown ownership server.

Call this once in test/test_helper.exs.