# `DoubleDown.Double`
[🔗](https://github.com/mccraigmccraig/double_down/blob/main/lib/double_down/double.ex#L1)

Mox-style expect/stub handler declarations with immediate effect.

Each `expect` and `stub` call writes directly to NimbleOwnership —
no builder struct, no `install!` step. Functions return the contract
module atom for Mimic-style piping.

## Basic usage

    DoubleDown.Double.expect(MyContract, :get_thing, fn [id] -> %Thing{id: id} end)
    DoubleDown.Double.stub(MyContract, :list, fn [_] -> [] end)

    # ... run code under test ...

    DoubleDown.Double.verify!()

## Piping

All functions return the contract module, so you can pipe:

    MyContract
    |> DoubleDown.Double.expect(:get_thing, fn [id] -> %Thing{id: id} end)
    |> DoubleDown.Double.stub(:list, fn [_] -> [] end)

## Sequenced expectations

Successive calls to `expect` for the same operation queue handlers
that are consumed in order:

    MyContract
    |> DoubleDown.Double.expect(:get_thing, fn [_] -> {:error, :not_found} end)
    |> DoubleDown.Double.expect(:get_thing, fn [id] -> %Thing{id: id} end)

    # First call returns :not_found, second returns the thing

## Repeated expectations

Use `times: n` when the same function should handle multiple calls:

    DoubleDown.Double.expect(MyContract, :check, fn [_] -> :ok end, times: 3)

## Expects + stubs

When an operation has both expects and a stub, expects are consumed
first; once exhausted, the stub handles all subsequent calls:

    MyContract
    |> DoubleDown.Double.expect(:get, fn [_] -> :first end)
    |> DoubleDown.Double.stub(:get, fn [_] -> :default end)

## Fallback handlers

A fallback handles any operation without a specific expect, per-op
fake, or per-op stub. Use `Double.fallback/2..4` to install one:

    # StatefulHandler module (recommended)
    DoubleDown.Repo
    |> DoubleDown.Double.fallback(Repo.OpenInMemory)
    |> DoubleDown.Double.expect(:insert, fn [changeset] ->
      {:error, Ecto.Changeset.add_error(changeset, :email, "taken")}
    end)

    # Stateless function fallback
    MyContract
    |> DoubleDown.Double.fallback(fn _contract, operation, args ->
      case {operation, args} do
        {:list, [_]} -> []
        {:count, []} -> 0
      end
    end)

See `fallback/2` for all supported forms (StatefulHandler module,
StatelessHandler module, module implementation, stateful function,
stateless function).

Dispatch priority: rejects > expects > per-op fakes > per-op stubs > fallback > raise.
Fallback types are mutually exclusive — setting one replaces the other.

## Passthrough expects

When a fallback/fake is configured, pass `:passthrough` instead of
a function to delegate while still consuming the expect for
`verify!` counting:

    MyContract
    |> DoubleDown.Double.fallback(MyApp.Impl)
    |> DoubleDown.Double.expect(:get, :passthrough, times: 2)

## Multi-contract

    DoubleDown.Repo
    |> DoubleDown.Double.fallback(&Repo.OpenInMemory.dispatch/4, Repo.OpenInMemory.new())
    |> DoubleDown.Double.expect(:insert, fn [cs] -> {:error, :taken} end)

    QueriesContract
    |> DoubleDown.Double.expect(:get_record, fn [id] -> %Record{id: id} end)

## Relationship to Mox

| Mox / Mimic | DoubleDown.Double |
|-----|-----------------|
| `expect(Mock, :fn, n, fun)` | `expect(Contract, :fn, fun, times: n)` |
| `reject(Mock, :fn, arity)` | `reject(Contract, :fn)` |
| `stub(Mock, :fn, fun)` | `stub(Contract, :fn, fun)` — per-operation |
| (no equivalent) | `fallback(Contract, fn op, args -> ... end)` — stateless fallback |
| (no equivalent) | `fallback(Contract, fn op, args, state -> ... end, init)` — stateful fallback |
| (no equivalent) | `fallback(Contract, ImplModule)` — module fallback |
| `verify!()` | `verify!()` |
| `verify_on_exit!()` | `verify_on_exit!()` |
| `Mox.defmock(Mock, for: Behaviour)` | Not needed |
| `Application.put_env(...)` | Not needed |

## Relationship to existing APIs

This is a higher-level convenience built on `set_stateful_handler`.
It does not replace `set_stateless_handler` or `set_stateful_handler` —
those remain for cases that don't fit the expect/stub/fake/fallback pattern.

## Known limitations

**FunctionClauseError in fallback bodies:** When a stub or fake
fallback function has no matching clause for an operation, DoubleDown
catches the `FunctionClauseError` and raises a helpful "Unexpected
call" error instead. However, if a fallback clause *does* match but
its body internally calls a function that raises `FunctionClauseError`,
that exception is also caught and misreported as "Unexpected call".
This is a known limitation shared with Mox. If you see a surprising
"Unexpected call" error, check whether your fallback body contains
code that might raise `FunctionClauseError`.

# `allow`

```elixir
@spec allow(module(), pid(), pid() | (-&gt; pid() | [pid()])) :: :ok | {:error, term()}
```

Allow a child process to use the current process's test doubles.

Delegates to `DoubleDown.Testing.allow/3`. Use this when spawning
Tasks or other processes that need to dispatch through the same
test handlers.

    {:ok, pid} = MyApp.Worker.start_link([])
    DoubleDown.Double.allow(MyContract, pid)

Also accepts a lazy pid function for processes that don't exist
yet at setup time:

    DoubleDown.Double.allow(MyContract, fn -> GenServer.whereis(MyWorker) end)

# `defer`

```elixir
@spec defer((-&gt; term())) :: DoubleDown.Contract.Dispatch.Defer.t()
```

Create a deferred execution marker for use in expect/stub/fake bodies.

When a handler body needs to call another facade (including
`Backend.Repo`), wrapping the call in `defer/1` avoids deadlocking
the NimbleOwnership GenServer. The deferred function runs outside
the lock, in the calling process, after the handler's state update
has been committed.

    # Stateful fake that calls another contract:
    Counter
    |> Double.fallback(fn
      _contract, :increment, [n], count ->
        {Double.defer(fn ->
          greeting = Greeter.greet("from_counter")
          {greeting, n}
        end), count + n}

      _contract, :get_count, [], count ->
        {count, count}
    end, 0)

    # Stateless stub that calls Repo:
    Double.stub(MyContract, :find_config, fn [org_id] ->
      Double.defer(fn ->
        Backend.Repo.get_by(Config, organisation_id: org_id)
      end)
    end)

**When you need `defer`:** any time an expect, stub, or fake body
calls through a facade — the handler runs inside a NimbleOwnership
lock, and a nested dispatch would deadlock without it.

# `dynamic`

```elixir
@spec dynamic(module()) :: module()
```

Set up a dynamically-faked module with its original implementation
as the fallback.

Requires the module to have been set up with
`DoubleDown.DynamicFacade.setup/1`. Layer expects and stubs on top:

    SomeClient
    |> DoubleDown.Double.dynamic()
    |> DoubleDown.Double.expect(:fetch, fn [_] -> {:error, :timeout} end)

Calls without a matching expect or stub delegate to the original
module's implementation.

Returns the module for piping.

# `expect`

```elixir
@spec expect(
  module(),
  atom(),
  DoubleDown.Double.Types.expect_fun() | :passthrough,
  keyword()
) ::
  module()
```

Add an expectation for a contract operation.

The responder function may be:

  * **1-arity** `fn [args] -> result end` — stateless, returns a bare result
  * **2-arity** `fn [args], state -> {result, new_state} end` — reads and
    updates the stateful fake's state. Requires a stateful fallback (`fallback/2..4`) first.
  * **3-arity** `fn [args], state, all_states -> {result, new_state} end` —
    same as 2-arity plus a read-only snapshot of all contract states for
    cross-contract access. Requires a stateful fallback (`fallback/2..4`) first.

Expectations are consumed in order — the first `expect` for an
operation handles the first call, the second handles the second,
and so on.

Instead of a function, pass `:passthrough` to delegate to the
fallback (fn, stateful, or module) while still consuming the
expect for `verify!` counting.

Returns the contract module for piping.

## Options

  * `:times` — enqueue the same function `n` times (default 1).
    Equivalent to calling `expect` `n` times with the same function.

# `fake`

```elixir
@spec fake(module(), atom(), DoubleDown.Double.Types.fake_fun()) :: module()
```

Add a per-operation fake for a contract operation.

Overrides a single operation with a stateful function that reads
and updates the fallback's state. Requires a stateful fallback
to be installed first via `fallback/3`:

    DoubleDown.Repo
    |> DoubleDown.Double.fallback(&Repo.OpenInMemory.dispatch/4, Repo.OpenInMemory.new())
    |> DoubleDown.Double.fake(:insert, fn [changeset], state ->
      {{:error, add_error(changeset, :email, "taken")}, state}
    end)

The function may be:

  * **2-arity** `fn [args], state -> {result, new_state} end`
  * **3-arity** `fn [args], state, all_states -> {result, new_state} end`
    (cross-contract state access)

Per-op fakes are permanent (not consumed like expects) and can
return `Double.passthrough()` to delegate to the fallback.
Setting a per-op fake twice for the same operation replaces the
previous one.

For whole-contract fallback handlers, see `fallback/2..4`.

Dispatch priority: rejects > expects > per-op fakes > per-op stubs > fallback > raise.

Returns the contract module for piping.

# `fallback`

```elixir
@spec fallback(module(), function() | module()) :: module()
```

Install a whole-contract fallback handler.

The fallback handles any operation not covered by an `expect`,
per-op `fake`, or per-op `stub`. Several forms are supported:

## StatefulHandler module (recommended for stateful fallbacks)

A module implementing `DoubleDown.Contract.Dispatch.StatefulHandler`.
The module's `new/2` builds initial state, and its `dispatch/4` or
`dispatch/5` handles operations:

    # Default state
    DoubleDown.Double.fallback(MyContract, Repo.OpenInMemory)

    # With seed data
    DoubleDown.Double.fallback(MyContract, Repo.OpenInMemory, [%User{id: 1}])

    # With seed data and options
    DoubleDown.Double.fallback(MyContract, Repo.OpenInMemory, [%User{id: 1}],
      fallback_fn: fn _contract, :all, [User], state -> Map.values(state[User]) end
    )

## StatelessHandler module

A module implementing `DoubleDown.Contract.Dispatch.StatelessHandler`.
The module's `new/2` builds a stateless dispatch function:

    # Writes only — reads will raise
    DoubleDown.Double.fallback(MyContract, DoubleDown.Repo.Stub)

    # With a fallback function for reads
    DoubleDown.Double.fallback(MyContract, DoubleDown.Repo.Stub,
      fn _contract, :all, [User] -> [] end)

## Module implementation

A module implementing the contract's `@behaviour` (but not a
StatefulHandler or StatelessHandler). All operations delegate via
`apply(module, operation, args)`:

    DoubleDown.Double.fallback(MyContract, MyApp.Impl)

**Mimic-style limitation:** if the module's `:bar` internally calls
`:foo`, and you've stubbed `:foo`, the module won't see your stub —
it calls its own `:foo` directly.

## Stateful function

A 4-arity `fn contract, operation, args, state -> {result, new_state} end`
or 5-arity `fn contract, operation, args, state, all_states -> {result, new_state} end`
with initial state:

    DoubleDown.Double.fallback(MyContract, &handler/4, initial_state)

## Stateless function

A 3-arity `fn contract, operation, args -> result end`:

    DoubleDown.Double.fallback(MyContract, fn _contract, operation, args ->
      case {operation, args} do
        {:list, [_]} -> []
        {:count, []} -> 0
      end
    end)

Fallback types are mutually exclusive — setting one replaces the other.

Dispatch priority: rejects > expects > per-op fakes > per-op stubs > fallback > raise.

Returns the contract module for piping.

# `fallback`

```elixir
@spec fallback(module(), function() | module(), term()) :: module()
```

# `fallback`

```elixir
@spec fallback(module(), module(), term(), keyword()) :: module()
```

# `passthrough`

```elixir
@spec passthrough() :: DoubleDown.Contract.Dispatch.Passthrough.t()
```

Return a passthrough sentinel for use in expect responders.

When returned from an expect responder, delegates the call to the
fallback/fake as if the expect had been registered with `:passthrough`.
The expect is still consumed for `verify!` counting.

This enables conditional passthrough — the responder can inspect
the state and decide whether to handle the call or delegate:

    DoubleDown.Repo
    |> Double.fallback(&Repo.OpenInMemory.dispatch/4, Repo.OpenInMemory.new())
    |> Double.expect(:insert, fn [changeset], state ->
      if duplicate?(state, changeset) do
        {{:error, add_error(changeset, :email, "taken")}, state}
      else
        Double.passthrough()
      end
    end)

# `reject`

```elixir
@spec reject(module(), atom(), non_neg_integer()) :: module()
```

Reject an operation/arity — assert it must not be called.

If the rejected operation is called with the specified arity during
the test, an error is raised immediately. If it is never called,
`verify!` passes (there are no expectations to consume).

The `arity` parameter is the number of arguments the operation
receives (matching Mimic's `reject/3` signature). Different arities
of the same operation can be independently rejected or expected:

    MyContract
    |> DoubleDown.Double.fallback(fn _contract, :fetch, [_id] -> nil end)
    |> DoubleDown.Double.reject(:delete, 1)

    # fetch/1 still works, delete/1 raises if called

Returns the contract module for piping.

# `stub`

```elixir
@spec stub(module(), atom(), DoubleDown.Double.Types.stub_fun()) :: module()
```

Add a per-operation stub for a contract operation.

The function receives the argument list and returns the result.
Stubs are stateless — for stateful per-operation overrides, use
`fake/3` or `expect/4` with a 2-arity or 3-arity responder.

Stubs handle any number of calls and are used after all expectations
for an operation are consumed. Setting a stub twice for the same
operation replaces the previous one.

The stub may return `Double.passthrough()` to delegate to the
fallback for that specific call.

    DoubleDown.Double.stub(MyContract, :list, fn [_] -> [] end)

For whole-contract fallback handlers, see `fallback/2..4`.

Dispatch priority: rejects > expects > per-op fakes > per-op stubs > fallback > raise.

Returns the contract module for piping.

# `verify!`

```elixir
@spec verify!() :: :ok
```

Verify that all expectations have been consumed.

Reads the current handler state for each contract and checks that
all expect queues are empty. Stubs are not checked — they are
allowed to be called zero or more times.

Raises with a descriptive message if any expectations remain
unconsumed.

Returns `:ok` if all expectations are satisfied.

# `verify!`

```elixir
@spec verify!(pid()) :: :ok
```

Verify expectations for a specific process.

Same as `verify!/0` but checks the expectations owned by `pid`
instead of the calling process. Used internally by `verify_on_exit!/0`.

# `verify_on_exit!`

```elixir
@spec verify_on_exit!(map()) :: :ok
```

Register an `on_exit` callback that verifies expectations after
each test.

Call this in a `setup` block so that tests which forget to call
`verify!/0` explicitly still fail on unconsumed expectations:

    setup :verify_on_exit!

Or equivalently:

    setup do
      DoubleDown.Double.verify_on_exit!()
    end

The verification runs in the on_exit callback (a separate process),
using the test pid captured at setup time.

---

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