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

Dynamic dispatch facades for existing modules.

Enables Mimic-style bytecode interception — replace any module with
a dispatch shim at test time, then use the full `DoubleDown.Double`
API (expects, stubs, fakes, stateful responders, passthrough) without
defining a contract or facade.

## Setup

Call `setup/1` in `test/test_helper.exs` **before** `ExUnit.start()`:

    DoubleDown.DynamicFacade.setup(MyApp.EctoRepo)
    DoubleDown.DynamicFacade.setup(SomeThirdPartyModule)

    ExUnit.start()

Setup is **lazy**: modules are registered for potential shimming but
no bytecode manipulation happens until a test installs a handler via
`DoubleDown.Double.fallback/2`, `expect/3`, or `stub/2`. This means
a test run that never exercises a module pays zero shimming cost.

## Usage in tests

    setup do
      DoubleDown.Double.fallback(MyApp.EctoRepo, DoubleDown.Repo.OpenInMemory)
      :ok
    end

    test "insert then get" do
      {:ok, user} = MyApp.EctoRepo.insert(User.changeset(%{name: "Alice"}))
      assert ^user = MyApp.EctoRepo.get(User, user.id)
    end

Tests that don't install a handler get the original module's
behaviour — zero impact on unrelated tests.

## Struct modules

If the original module defines a struct (`defstruct`), the shim
preserves full struct support:

  * `%Module{}` literal syntax works at compile time in tests
  * `__info__(:struct)` returns correct field metadata
  * `@enforce_keys` and default values are preserved
  * `__struct__/0` and `__struct__/1` calls route through
    `dispatch/3`, so `Double.fallback` / `Double.expect` handlers
    can intercept struct construction at runtime

## Behaviour and macro modules

  * **`@behaviour` declarations** are copied from the original
    module to the shim, so behaviour-based dispatch and compliance
    checks work correctly.
  * **Macros** (`defmacro`) are proxied via `defmacro` wrappers
    that delegate to the original implementation. Macros expand at
    compile time so they always use the original — they cannot be
    intercepted by `Double` handlers.

## Constraints

- Call `setup/1` before tests start (in `test_helper.exs`). Bytecode
  replacement is VM-global; calling it during async tests may cause
  flaky behaviour.
- Cannot set up dynamic facades for DoubleDown contracts (use
  `DoubleDown.ContractFacade` instead), DoubleDown internals,
  NimbleOwnership, or Erlang/OTP modules.

## See also

  * `DoubleDown.ContractFacade` — dispatch facades for `defcallback` contracts
    (typed, LSP-friendly, recommended for new code).
  * `DoubleDown.BehaviourFacade` — dispatch facades for vanilla
    `@behaviour` modules (typed, but no pre_dispatch or combined
    contract + facade).

# `dispatch`

```elixir
@spec dispatch(module(), atom(), [term()]) :: term()
```

Dispatch a call through the dynamic facade.

Called by generated shims. Checks NimbleOwnership for a test
handler, falls back to the original module (`Module.__dd_original__`).

# `ensure_shimmed`

```elixir
@spec ensure_shimmed(module()) :: :ok
```

Ensure a lazily-registered module is fully shimmed.

If the module is in the lazy set, performs the bytecode manipulation
(rename original + create dispatch shim) and moves it to the shimmed
set. If the module is already shimmed or not registered, this is a
no-op.

Called by `DoubleDown.Testing.set_meta/2` when a handler is first
installed, and as a safety net in `dispatch/3`.

# `setup`

```elixir
@spec setup(module()) :: :ok
```

Register a module for lazy dynamic dispatch.

The module is registered for potential shimming but no bytecode
manipulation happens yet. The actual shim (rename original + create
dispatch shim) fires lazily when a test first installs a handler via
`DoubleDown.Double.fallback/2`, `expect/3`, or `stub/2`.

Call this in `test/test_helper.exs` **before** `ExUnit.start()`.

After setup, use the full `DoubleDown.Double` API:

    DoubleDown.Double.fallback(MyModule, handler)
    DoubleDown.Double.expect(MyModule, :op, fn [args] -> result end)

Tests that don't install a handler get the original module's
behaviour automatically — and never pay the shimming cost.

# `setup?`

```elixir
@spec setup?(module()) :: boolean()
```

Check whether a module has been registered for dynamic dispatch.

Returns `true` if the module is either lazily registered (not yet
shimmed) or fully shimmed.

---

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