### Set Mode from Context Example Source: https://hexdocs.pm/double_down/DoubleDown.Testing.md Example of using `set_mode_from_context/1` as a setup callback, followed by registering a module handler. This pattern ensures correct mode selection for each test. ```elixir use ExUnit.Case, async: false setup :set_mode_from_context setup do DoubleDown.Testing.set_module_handler(MyApp.Repo, MyApp.Repo.InMemory) :ok end ``` -------------------------------- ### start Source: https://hexdocs.pm/double_down/DoubleDown.Testing.md Starts the DoubleDown ownership server. This should be called once in `test/test_helper.exs`. ```APIDOC ## `start` ### Description Starts the DoubleDown ownership server. Call this once in `test/test_helper.exs`. ### Function Signature ```elixir @spec start() :: {:ok, pid()} | {:error, term()} ``` ``` -------------------------------- ### Example Test with Dynamic Facade Source: https://hexdocs.pm/double_down/dynamic.md Demonstrates using a dynamic facade with a stateful fake for database operations. The setup ensures that insert and get operations work as expected. ```elixir setup do # Stateful fake DoubleDown.Double.fallback(MyApp.EctoRepo, DoubleDown.Repo.InMemory) :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 ``` -------------------------------- ### Setup DoubleDown for Testing Source: https://hexdocs.pm/double_down/phoenix.md Initial setup for DoubleDown in test files. This configures DynamicFacade and starts the testing environment. ```elixir DoubleDown.DynamicFacade.setup(MyApp.Orders) DoubleDown.DynamicFacade.setup(MyApp.Accounts) {:ok, _} = DoubleDown.Testing.start() ExUnit.start() ``` -------------------------------- ### Configure DoubleDown with DynamicFacade in test_helper.exs Source: https://hexdocs.pm/double_down/phoenix.md If using DynamicFacade, set it up in `test_helper.exs` and start DoubleDown's testing utilities. The `UnitConnCase` setup remains the same for installing the in-memory fallback. ```elixir DoubleDown.DynamicFacade.setup(MyApp.Repo) {:ok, _} = DoubleDown.Testing.start() ExUnit.start() ``` ```elixir setup do DoubleDown.Double.fallback(MyApp.Repo, DoubleDown.Repo.InMemory) :ok end ``` -------------------------------- ### Multi-contract Setup with Fallback and Expect Source: https://hexdocs.pm/double_down/DoubleDown.Double.md Configure handlers for multiple contracts. This example sets up a fallback for `DoubleDown.Repo` using a stateful function and `Repo.OpenInMemory.new()`, and an `expect` for `:insert`. It also sets up an `expect` for `:get_record` on `QueriesContract`. ```elixir 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) ``` -------------------------------- ### Basic Repo Setup for Writes and PK Reads Source: https://hexdocs.pm/double_down/repo-doubles.md Use this setup when your tests only require writes and primary key lookups. No fallback is needed in this scenario. ```elixir setup do DoubleDown.Double.fallback(DoubleDown.Repo, DoubleDown.Repo.OpenInMemory) :ok end test "insert then get by PK" do {:ok, user} = MyApp.Repo.insert(User.changeset(%{name: "Alice"})) assert ^user = MyApp.Repo.get(User, user.id) end ``` -------------------------------- ### ExMachina Integration for Test Setup Source: https://hexdocs.pm/double_down/DoubleDown.Repo.InMemory.md This setup block shows how to integrate the in-memory repo with ExMachina factories to pre-populate test data. It then demonstrates basic tests for listing, finding, and counting users. ```elixir setup do DoubleDown.Double.fallback(DoubleDown.Repo, DoubleDown.Repo.InMemory) insert(:user, name: "Alice", email: "alice@example.com") insert(:user, name: "Bob", email: "bob@example.com") :ok end test "lists all users" do assert [_, _] = MyApp.Repo.all(User) end test "finds user by email" do assert %User{name: "Alice"} = MyApp.Repo.get_by(User, email: "alice@example.com") end test "count users" do assert 2 = MyApp.Repo.aggregate(User, :count, :id) end ``` -------------------------------- ### Start DoubleDown Ownership Server Source: https://hexdocs.pm/double_down/DoubleDown.Testing.md Call this once in `test/test_helper.exs` to start the ownership server. Returns `{:ok, pid()}` on success or `{:error, term()}` on failure. ```elixir @spec start() :: {:ok, pid()} | {:error, term()} ``` -------------------------------- ### Installing a Stateful Fallback Handler Source: https://hexdocs.pm/double_down/DoubleDown.Double.md Use `fallback/2` with a StatefulHandler module to provide a default behavior for operations without specific expectations or stubs. This example uses `Repo.OpenInMemory` for stateful handling and defines a specific `expect` for `:insert`. ```elixir DoubleDown.Repo |> DoubleDown.Double.fallback(Repo.OpenInMemory) |> DoubleDown.Double.expect(:insert, fn [changeset] -> {:error, Ecto.Changeset.add_error(changeset, :email, "taken")} end) ``` -------------------------------- ### Start DoubleDown Testing Server Source: https://hexdocs.pm/double_down/testing.md Starts the NimbleOwnership GenServer for process-scoped test handler isolation. This should be run once in `test/test_helper.exs`. ```elixir {:ok, _} = DoubleDown.Testing.start() ``` -------------------------------- ### Setup Dynamic Facade Source: https://hexdocs.pm/double_down/DoubleDown.DynamicFacade.md Call setup/1 in test/test_helper.exs before ExUnit.start() to enable dynamic facades for modules. This is required for bytecode replacement to function correctly. ```elixir DoubleDown.DynamicFacade.setup(MyApp.EctoRepo) DoubleDown.DynamicFacade.setup(SomeThirdPartyModule) ExUnit.start() ``` -------------------------------- ### setup?/1 Source: https://hexdocs.pm/double_down/DoubleDown.DynamicFacade.md Checks if a module has already been set up for dynamic dispatch. ```APIDOC ## setup?/1 ### Description Check whether a module has been set up for dynamic dispatch. ### Parameters #### Path Parameters - **module** (module()) - Required - The module to check for dynamic dispatch setup. ``` -------------------------------- ### Test GenServer Fetching Todo via Contract Source: https://hexdocs.pm/double_down/process-sharing.md Example of testing a GenServer that dispatches through a contract. It stubs a function, starts the worker, and allows sharing the double with the worker's process. ```elixir defmodule MyApp.WorkerTest do use ExUnit.Case, async: true setup do MyApp.Todos |> DoubleDown.Double.stub(:get_todo, fn [id] -> {:ok, %Todo{id: id}} end) {:ok, pid} = MyApp.Worker.start_link([]) DoubleDown.Double.allow(MyApp.Todos, self(), pid) %{worker: pid} end test "worker fetches todo via contract", %{worker: pid} do assert {:ok, %Todo{id: "42"}} = MyApp.Worker.fetch(pid, "42") end end ``` -------------------------------- ### Configuration and Test Setup for Combined Pattern Source: https://hexdocs.pm/double_down/getting-started.md Configure the implementation and set up test doubles by referencing the module that serves as both contract and facade. ```elixir # config config :my_app, MyApp.Todos, impl: MyApp.Todos.Ecto # test setup DoubleDown.Double.fallback(MyApp.Todos, fn _contract, :get_todo, [id] -> {:ok, %Todo{id: id}} end) ``` -------------------------------- ### Setup Dynamic Dispatch Facade Source: https://hexdocs.pm/double_down/DoubleDown.DynamicFacade.md Sets up a dynamic dispatch facade for a module by backing up the original and replacing it with a shim. This function must be called before ExUnit.start() as bytecode replacement is VM-global. ```elixir DoubleDown.DynamicFacade.setup(module()) ``` -------------------------------- ### Start Double Down Testing Server Source: https://hexdocs.pm/double_down/index.html Starts the Double Down ownership server in the test environment. This is a prerequisite for running tests that use Double Down's testing utilities. ```elixir DoubleDown.Testing.start() ``` -------------------------------- ### Set up a Module for Dynamic Dispatch Facades Source: https://hexdocs.pm/double_down/changelog.md Use Dynamic.setup/1 to replace a module's bytecode with a dispatch shim, enabling the full DoubleDown API. Call this in your test_helper.exs before ExUnit.start(). This setup is async-safe and refuses certain module types like DoubleDown contracts or OTP modules. ```elixir Dynamic.setup(MyModule) ``` -------------------------------- ### Configuration and Test Setup for Separate Pattern Source: https://hexdocs.pm/double_down/getting-started.md Configure and set up test doubles by referencing the contract module, while callers use the facade module. ```elixir # config config :my_app, MyApp.Todos.Contract, impl: MyApp.Todos.Ecto # test setup DoubleDown.Double.fallback(MyApp.Todos.Contract, fn _contract, :get_todo, [id] -> {:ok, %Todo{id: id}} end) ``` -------------------------------- ### Insert a record using the facade Source: https://hexdocs.pm/double_down/DoubleDown.Repo.md After defining the facade, you can use its methods to interact with the database. This example shows inserting a user record. ```elixir changeset = User.changeset(%User{}, attrs) {:ok, user} = MyApp.Repo.insert(changeset) ``` -------------------------------- ### Setup Dynamic Facades in test_helper.exs Source: https://hexdocs.pm/double_down/dynamic.md Call DynamicFacade.setup/1 in test/test_helper.exs before ExUnit.start(). This replaces the original module with a dispatch shim, making the original module name the implicit contract. ```elixir # test/test_helper.exs DoubleDown.DynamicFacade.setup(MyApp.EctoRepo) DoubleDown.DynamicFacade.setup(SomeThirdPartyClient) ExUnit.start() {:ok, _} = DoubleDown.Testing.start() ``` -------------------------------- ### Set up InMemory Repo in Test Source: https://hexdocs.pm/double_down/repo-doubles.md Configure the DoubleDown.Double.fallback to use DoubleDown.Repo.InMemory in your test setup. This allows ExMachina factories to write records that can be read back. ```elixir defmodule MyApp.SomeTest do use ExUnit.Case, async: true import MyApp.Factory setup do DoubleDown.Double.fallback(DoubleDown.Repo, DoubleDown.Repo.InMemory) :ok end test "factory-inserted records are readable" do insert(:user, name: "Alice", email: "alice@example.com") insert(:user, name: "Bob", email: "bob@example.com") # All bare-schema reads work — no fallback needed assert [_, _] = MyApp.Repo.all(User) assert %User{name: "Alice"} = MyApp.Repo.get_by(User, email: "alice@example.com") assert 2 = MyApp.Repo.aggregate(User, :count, :id) end test "read-after-write consistency" do user = insert(:user, name: "Alice") assert ^user = MyApp.Repo.get(User, user.id) end test "failure simulation over factory data" do insert(:user, name: "Alice") insert(:user, name: "Bob") # Intercept the next insert! to simulate a constraint error DoubleDown.Double.expect(DoubleDown.Repo, :insert!, fn [struct] -> cs = Ecto.Changeset.change(struct) |> Ecto.Changeset.add_error(:name, "taken") raise Ecto.InvalidChangesetError, action: :insert, changeset: cs end) assert_raise Ecto.InvalidChangesetError, fn -> insert(:user, name: "Carol") end # Existing records are unaffected assert 2 = MyApp.Repo.aggregate(User, :count, :id) end end ``` -------------------------------- ### Install Per-Operation Fake Source: https://hexdocs.pm/double_down/changelog.md Installs a permanent stateful override for a single operation. Requires a stateful fallback fake to be installed first. Supports 3-arity function for cross-contract state access. ```elixir Double.fake(contract, :operation, fn [args], state -> {result, new_state} end) ``` ```elixir Double.fake(contract, :operation, fn [args], state, all_states -> {result, new_state} end) ``` -------------------------------- ### Register on_exit callback with verify_on_exit!/0 Source: https://hexdocs.pm/double_down/DoubleDown.Double.md Call this in a `setup` block to ensure tests fail on unconsumed expectations. The verification runs in the on_exit callback. ```elixir @spec verify_on_exit!(map()) :: :ok ``` ```elixir setup :verify_on_exit! ``` ```elixir setup do DoubleDown.Double.verify_on_exit!() end ``` -------------------------------- ### Install Whole-Contract Fallback Handler Source: https://hexdocs.pm/double_down/DoubleDown.Double.md Use `fallback/2` to install a handler for any operation not covered by expects, fakes, or stubs. This can be a function or a module, and it handles operations not explicitly defined. ```elixir DoubleDown.Repo |> DoubleDown.Double.fallback(&Repo.OpenInMemory.dispatch/4, Repo.OpenInMemory.new()) ``` -------------------------------- ### Global Mode Setup Warning Source: https://hexdocs.pm/double_down/DoubleDown.Testing.md Illustrates a common mistake where `set_mode_to_global/0` is called within `setup_all/0`, leading to handler registration failures in subsequent `setup/0` blocks because `self()` is not the shared owner. ```elixir # BROKEN — setup_all and setup run in different processes setup_all do DoubleDown.Testing.set_mode_to_global() :ok end setup do # This RAISES — self() is not the shared owner DoubleDown.Testing.set_module_handler(MyContract, MyImpl) end ``` -------------------------------- ### Integrating DoubleDown.Double and DoubleDown.Log Source: https://hexdocs.pm/double_down/logging.md Use `DoubleDown.Double` for fail-fast validation and return values, and `DoubleDown.Log` for after-the-fact inspection of dispatched operations. This example shows setting up a double, enabling logging, running code, verifying the double, and then verifying the log entries. ```elixir # Set up double DoubleDown.Double.expect(MyContract, :create, fn [p] -> {:ok, struct!(Thing, p)} end) DoubleDown.Testing.enable_log(MyContract) # Run code under test MyModule.do_work(params) # Verify expectations consumed DoubleDown.Double.verify!() # Verify log entries match expected patterns DoubleDown.Log.match(:create, fn {_, _, _, {:ok, %Thing{}}} -> true end) |> DoubleDown.Log.verify!(MyContract) ``` -------------------------------- ### Automatic Verification on Exit Source: https://hexdocs.pm/double_down/testing.md Use `verify_on_exit!/0` in a setup block to automatically verify that all expectations have been consumed after each test. This catches forgotten `verify!` calls. ```elixir setup :verify_on_exit! # or equivalently: setup do DoubleDown.Double.verify_on_exit! traditional end ``` -------------------------------- ### Configuration and Test Setup for Behaviour Facade Source: https://hexdocs.pm/double_down/getting-started.md Configure and set up test doubles by referencing the behaviour module, which acts as the contract. Callers interact with the generated facade module. ```elixir # config config :my_app, MyApp.Todos.Behaviour, impl: MyApp.Todos.Ecto # test setup DoubleDown.Double.fallback(MyApp.Todos.Behaviour, fn _contract, :get_todo, [id] -> {:ok, %Todo{id: id}} end) ``` -------------------------------- ### Select Test Mode from Context Source: https://hexdocs.pm/double_down/changelog.md Selects private or global mode based on the test context's :async flag. Use as a setup callback in tests. ```elixir setup :set_mode_from_context ``` -------------------------------- ### Automatic Verification on Exit with DoubleDown.Double.verify_on_exit!/0 Source: https://hexdocs.pm/double_down/changelog.md Register an `on_exit` callback using `DoubleDown.Double.verify_on_exit!/0` to automatically verify all expectations after each test. This can be used as `setup :verify_on_exit!`. ```elixir setup :verify_on_exit! ``` -------------------------------- ### Setup and Test for Logging Failures and Successes Source: https://hexdocs.pm/double_down/repo-testing.md Configure DoubleDown to fallback to an in-memory repository and expect a specific error for inserts. Enable logging for the repository to capture events. The test asserts that both the expected error and a subsequent successful insertion are logged. ```elixir setup do DoubleDown.Repo |> DoubleDown.Double.fallback(DoubleDown.Repo.InMemory) |> DoubleDown.Double.expect(:insert, fn [changeset] -> {:error, Ecto.Changeset.add_error(changeset, :email, "taken")} end) DoubleDown.Testing.enable_log(DoubleDown.Repo) :ok end test "logs the failure then the success" do changeset = User.changeset(%User{}, %{email: "alice@example.com"}) assert {:error, _} = MyApp.Repo.insert(changeset) assert {:ok, %User{}} = MyApp.Repo.insert(changeset) DoubleDown.Double.verify!() DoubleDown.Log.match(:insert, fn {_, _, _, {:error, _}} -> true end) |> DoubleDown.Log.match(:insert, fn {_, _, _, {:ok, %User{id: id}}} when is_binary(id) -> true end) |> DoubleDown.Log.verify!(DoubleDown.Repo) end ``` -------------------------------- ### Setup DynamicFacade for Testing Source: https://hexdocs.pm/double_down/repo.md Configure DynamicFacade in your test_helper.exs to intercept calls to your Ecto Repo module. This approach does not require a separate facade module. ```elixir DoubleDown.DynamicFacade.setup(MyApp.EctoRepo) {:ok, _} = DoubleDown.Testing.start() ExUnit.start() ``` -------------------------------- ### Installing a Stateless Function Fallback Source: https://hexdocs.pm/double_down/DoubleDown.Double.md Define a stateless fallback handler using `fallback/2` with a function that takes the contract, operation, and arguments, returning a value based on the operation and arguments. ```elixir MyContract |> DoubleDown.Double.fallback(fn _contract, operation, args -> case {operation, args} do {:list, [_]} -> [] {:count, []} -> 0 end end) ``` -------------------------------- ### Set Up Stateless Handler for Testing Source: https://hexdocs.pm/double_down/DoubleDown.BehaviourFacade.md Use DoubleDown.Testing.set_stateless_handler to define how behaviour operations should be handled during testing. This allows you to mock behaviour implementations without needing a full OTP application setup. ```elixir setup do DoubleDown.Testing.set_stateless_handler(MyApp.Todos.Behaviour, fn _contract, operation, args -> case {operation, args} do {:get_item, [id]} -> {:ok, %{id: id}} {:list_items, []} -> [] end end) :ok end ``` -------------------------------- ### Layer expectations for failure simulation with OpenInMemory Source: https://hexdocs.pm/double_down/DoubleDown.Repo.OpenInMemory.md This example demonstrates how to layer expectations for failure simulation using OpenInMemory. It configures the repository to expect an `:insert` operation and return an error with a specific changeset error. ```elixir DoubleDown.Repo |> DoubleDown.Double.fallback(DoubleDown.Repo.OpenInMemory) |> DoubleDown.Double.expect(:insert, fn [changeset] -> {:error, Ecto.Changeset.add_error(changeset, :email, "taken")} end) ``` -------------------------------- ### Mix InMemory Repo with Context Stubs Source: https://hexdocs.pm/double_down/phoenix.md Using an InMemory Repo for write operations and context-level stubs for query-heavy reads. This setup is useful for testing scenarios involving both data persistence and specific read behaviors. ```elixir setup do # InMemory Repo for writes (insert, update, delete) DoubleDown.Double.fallback(MyApp.Repo, DoubleDown.Repo.InMemory) # Context-level stubs for query-heavy reads DoubleDown.Double.fallback(MyApp.Orders, fn _contract, op, args -> case {op, args} do {:list_active_orders, [_]} -> [] {:count_orders, [_]} -> 0 end end) :ok end ``` -------------------------------- ### Select Mode Based on Test Context Source: https://hexdocs.pm/double_down/DoubleDown.Testing.md Automatically selects between private (default, for `async: true`) and global mode based on the test context. This is the recommended setup callback for managing mode selection. ```elixir @spec set_mode_from_context(%{async: boolean()} | map()) :: :ok ``` -------------------------------- ### setup/1 Source: https://hexdocs.pm/double_down/DoubleDown.DynamicFacade.md Sets up a dynamic dispatch facade for a given module. This function should be called in `test/test_helper.exs` before `ExUnit.start()` to ensure VM-global bytecode replacement occurs correctly. ```APIDOC ## setup/1 ### Description Set up a dynamic dispatch facade for a module. Copies the original module to a backup (`Module.__dd_original__`) and replaces it with a shim that dispatches through `DoubleDown.DynamicFacade.dispatch/3`. Call this in `test/test_helper.exs` **before** `ExUnit.start()`. Bytecode replacement is VM-global — calling during async tests may cause flaky behaviour. 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. ### Parameters #### Path Parameters - **module** (module()) - Required - The module to set up for dynamic dispatch. ``` -------------------------------- ### Simplified DoubleDown.Double API for Stubbing Source: https://hexdocs.pm/double_down/changelog.md The DoubleDown.Double API has been simplified. `expect` and `stub` now directly write to NimbleOwnership with immediate effect. The `%DoubleDown.Double{}` struct, `new/0`, and `install!/1` have been removed. Functions return the contract module atom for piping. ```elixir MyContract |> DoubleDown.Double.stub(MyImpl) |> DoubleDown.Double.expect(:get, fn [id] -> %Thing{id: id} end) ``` -------------------------------- ### Define Contracts with DoubleDown.ContractFacade Source: https://hexdocs.pm/double_down/index.html Use `defcallback` to define domain-specific contracts. This example shows how to wrap the built-in `DoubleDown.Repo` and define a custom contract for managing todos. ```elixir defmodule MyApp.Repo do use DoubleDown.ContractFacade, contract: DoubleDown.Repo, otp_app: :my_app end defmodule MyApp.Todos.Model do use DoubleDown.ContractFacade, otp_app: :my_app defcallback active_todos(tenant_id :: String.t()) :: [Todo.t()] defcallback todo_exists?(tenant_id :: String.t(), title :: String.t()) :: boolean() end ``` -------------------------------- ### Implement Stateful Handler Behaviour Source: https://hexdocs.pm/double_down/DoubleDown.Contract.Dispatch.StatefulHandler.md Implement this behaviour to create a stateful fake handler. This example shows how to define a module that adheres to the `DoubleDown.Contract.Dispatch.StatefulHandler` behaviour, including the `new/2` and `dispatch/4` callbacks for managing in-memory state. ```elixir defmodule MyApp.InMemoryStore do @behaviour DoubleDown.Contract.Dispatch.StatefulHandler @impl true def new(seed, _opts), do: seed @impl true def dispatch(_contract, :get, [id], state), do: {Map.get(state, id), state} def dispatch(_contract, :put, [id, val], state), do: {:ok, Map.put(state, id, val)} end ``` -------------------------------- ### Get callback with options Source: https://hexdocs.pm/double_down/DoubleDown.Repo.md Defines an overloaded `get` callback that accepts an options keyword list for more specific retrieval by ID. ```elixir @callback get(queryable :: Ecto.Queryable.t(), id :: term(), opts :: keyword()) :: struct() | nil ``` -------------------------------- ### Ensure Handler Installed Source: https://hexdocs.pm/double_down/changelog.md Raises an error if a non-Double handler is already installed for the contract. Use exclusively or call reset() first. ```elixir Double.ensure_handler_installed(contract) ``` -------------------------------- ### Pre-dispatch Transform Example Source: https://hexdocs.pm/double_down/getting-started.md The `:pre_dispatch` option transforms arguments before dispatch. This example wraps 1-arity transaction functions into 0-arity thunks. ```elixir defcallback transact(fun_or_multi :: term(), opts :: keyword()) :: {:ok, term()} | {:error, term()}, pre_dispatch: fn args, facade_mod -> case args do [fun, opts] when is_function(fun, 1) -> [fn -> fun.(facade_mod) end, opts] [fun, _opts] when is_function(fun, 0) -> args _ -> args end end ``` -------------------------------- ### dynamic/1 Source: https://hexdocs.pm/double_down/DoubleDown.Double.md Sets up a dynamically-faked module with its original implementation as the fallback. This requires the module to have been set up with `DoubleDown.DynamicFacade.setup/1`. Expectations and stubs can then be layered on top. Calls without a matching expect or stub delegate to the original module's implementation. ```APIDOC ## dynamic/1 ### Description 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: ### Method `DoubleDown.Double.dynamic(module())` ### Parameters - **module**: The module to dynamically fake. ### Response Returns the module for piping. ### Example ```elixir 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. ``` -------------------------------- ### Get callback for retrieving a record by ID Source: https://hexdocs.pm/double_down/DoubleDown.Repo.md Defines the `get` callback for retrieving a single record by its ID. It returns the struct if found, or `nil` if not found. ```elixir @callback get(queryable :: Ecto.Queryable.t(), id :: term()) :: struct() | nil ``` -------------------------------- ### Get! callback for retrieving a record by ID Source: https://hexdocs.pm/double_down/DoubleDown.Repo.md Defines the `get!` callback for retrieving a single record by its ID. It returns the struct, raising an error if the record is not found. ```elixir @callback get!(queryable :: Ecto.Queryable.t(), id :: term()) :: struct() ``` -------------------------------- ### Get a record by ID using the facade Source: https://hexdocs.pm/double_down/DoubleDown.Repo.md Retrieve a record by its primary key using the `get!/2` function from the facade. This will raise an error if the record is not found. ```elixir user = MyApp.Repo.get!(User, user_id) ``` -------------------------------- ### Get! callback with options Source: https://hexdocs.pm/double_down/DoubleDown.Repo.md Defines an overloaded `get!` callback that accepts an options keyword list for more specific retrieval by ID. It returns the struct, raising an error if the record is not found. ```elixir @callback get!(queryable :: Ecto.Queryable.t(), id :: term(), opts :: keyword()) :: struct() ``` -------------------------------- ### Repo.InMemory Basic Usage Source: https://hexdocs.pm/double_down/repo-doubles.md Demonstrates basic usage of Repo.InMemory for inserting and then reading back data, asserting its presence and content. ```elixir setup do DoubleDown.Double.fallback(DoubleDown.Repo, DoubleDown.Repo.InMemory) :ok end test "insert then read back" do {:ok, user} = MyApp.Repo.insert(User.changeset(%{name: "Alice"})) assert ^user = MyApp.Repo.get(User, user.id) assert [^user] = MyApp.Repo.all(User) assert %User{} = MyApp.Repo.get_by(User, name: "Alice") end ``` -------------------------------- ### new Source: https://hexdocs.pm/double_down/DoubleDown.Contract.Dispatch.HandlerMeta.Stateful.md Create a new Stateful handler meta. Validates that `fun` is a 4 or 5-arity function. ```APIDOC ## new ### Description Create a new Stateful handler meta. Validates that `fun` is a 4 or 5-arity function. ### Signature ```elixir @spec new(DoubleDown.Contract.Dispatch.Types.stateful_fun(), term()) :: t() ``` ``` -------------------------------- ### Get contracts key Source: https://hexdocs.pm/double_down/DoubleDown.Contract.Dispatch.Keys.md NimbleOwnership key for the set of contracts with active `Double` expects/stubs. ```elixir def contracts_key() :: atom() ``` -------------------------------- ### Register Contract Handler Source: https://hexdocs.pm/double_down/DoubleDown.Testing.md Register a handler for a specific contract within your tests. This is typically done in a `setup` block. ```elixir DoubleDown.Testing.set_module_handler(MyApp.Todos, MyApp.Todos.InMemory) ``` -------------------------------- ### Get ownership server name Source: https://hexdocs.pm/double_down/DoubleDown.Contract.Dispatch.Keys.md The registered name of the NimbleOwnership GenServer. This is the canonical name used to interact with the ownership server. ```elixir def ownership_server() :: atom() ``` -------------------------------- ### Test Through a Supervision Tree using Global Mode Source: https://hexdocs.pm/double_down/process-sharing.md Demonstrates testing through a supervision tree using global mode when pids are not easily accessible. It sets up global mode, a fallback repository, and ensures private mode is restored on exit. ```elixir defmodule MyApp.PipelineIntegrationTest do use ExUnit.Case, async: false setup do DoubleDown.Testing.set_mode_to_global() DoubleDown.Double.fallback(DoubleDown.Repo, DoubleDown.Repo.InMemory) on_exit(fn -> DoubleDown.Testing.set_mode_to_private() end) start_supervised!(MyApp.Pipeline) :ok end test "pipeline processes events end-to-end" do MyApp.Pipeline.enqueue(%{type: :invoice, amount: 100}) # ... assert on results ... end end ``` -------------------------------- ### Define Combined Contract and Facade Source: https://hexdocs.pm/double_down/DoubleDown.ContractFacade.md Use DoubleDown.ContractFacade to define a module that acts as both the contract and its dispatch facade. This is the simplest setup for a contract. ```elixir defmodule MyApp.Todos do use DoubleDown.ContractFacade, otp_app: :my_app defcallback get_todo(id :: String.t()) :: {:ok, Todo.t()} | {:error, term()} defcallback list_todos() :: [Todo.t()] end ``` -------------------------------- ### Set Up Real Implementation for Integration Tests Source: https://hexdocs.pm/double_down/testing.md In integration tests that require the real implementation, use `DoubleDown.Double.fallback` to explicitly set the production module. This makes the choice to use the real implementation visible and intentional. ```elixir setup do DoubleDown.Double.fallback(MyApp.Todos, MyApp.Todos.Ecto) :ok end ``` -------------------------------- ### Create In-Memory Repository with Keyword Arguments Source: https://hexdocs.pm/double_down/DoubleDown.Repo.OpenInMemory.md Demonstrates the legacy keyword-only form for creating an in-memory repository, supporting seeding and fallback functions. ```elixir DoubleDown.Repo.OpenInMemory.new(seed: [%User{id: 1}], fallback_fn: fn ... end) ``` -------------------------------- ### Implementing StatelessHandler Behaviour Source: https://hexdocs.pm/double_down/DoubleDown.Contract.Dispatch.StatelessHandler.md Example implementation of the DoubleDown.Contract.Dispatch.StatelessHandler behaviour. The `new/2` callback defines how to build a dispatch function for handling operations. ```elixir defmodule MyApp.TestStore do @behaviour DoubleDown.Contract.Dispatch.StatelessHandler @impl true def new(fallback_fn, _opts) do fn contract, operation, args -> case {operation, args} do {:get, [id]} -> %{id: id} _ when is_function(fallback_fn) -> fallback_fn.(contract, operation, args) _ -> raise "unhandled" end end end end ``` -------------------------------- ### Protect Against Handler Overwrites Source: https://hexdocs.pm/double_down/changelog.md Ensures a handler is not already installed for the contract before setting one. Call Testing.reset() first to clear all handlers before reinstalling. ```elixir Testing.set_mode_from_context() ``` -------------------------------- ### Configure DoubleDown Repo Implementation Source: https://hexdocs.pm/double_down/migration.md Configure the DoubleDown.Repo implementation in config/config.exs and set it to nil in config/test.exs for testing. ```elixir config :my_app, DoubleDown.Repo, impl: MyApp.EctoRepo ``` ```elixir config :my_app, DoubleDown.Repo, impl: nil ``` -------------------------------- ### Basic DoubleDown Expect and Stub Usage Source: https://hexdocs.pm/double_down/testing.md Demonstrates basic usage of `expect` for ordered calls and `stub` for handling subsequent calls. `verify!()` is used to assert that all expectations were met. ```elixir setup do MyApp.Todos |> DoubleDown.Double.expect(:get_todo, fn [id] -> {:ok, %Todo{id: id}} end) |> DoubleDown.Double.stub(:list_todos, fn [_] -> [] end) :ok end test "..." do # ... run code under test ... DoubleDown.Double.verify!() end ``` -------------------------------- ### Basic Log Matching with DoubleDown.Log Source: https://hexdocs.pm/double_down/logging.md Use `DoubleDown.Log.match/2` to define positive matching clauses for log entries. `DoubleDown.Log.reject/2` can be used to explicitly exclude certain operations. `verify!/2` asserts that the log matches the defined expectations. ```elixir DoubleDown.Testing.enable_log(MyApp.Todos) # ... set up double and dispatch ... DoubleDown.Log.match(:create_todo, fn {_, _, [params], {:ok, %Todo{id: id}}} when is_binary(id) -> true end) |> DoubleDown.Log.reject(:delete_todo) |> DoubleDown.Log.verify!(MyApp.Todos) ``` -------------------------------- ### fallback/2 Source: https://hexdocs.pm/double_down/DoubleDown.Double.md Installs 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. ```APIDOC ## fallback/2 ### Description Install a whole-contract fallback handler. The fallback handles any operation not covered by an `expect`, per-op `fake`, or per-op `stub`. ### Method `DoubleDown.Double.fallback(module(), function() | module())` ### Parameters - **module**: The contract module. - **handler**: A function or module that will handle operations not covered by other mechanisms. ### Response Returns the contract module for piping. ### Example ```elixir # Using a function as a fallback DoubleDown.Repo |> DoubleDown.Double.fallback(fn _contract, _operation, _args, state -> # Default implementation or custom logic {:ok, state} end) # Using a module as a fallback (e.g., for stateful operations) DoubleDown.Repo |> DoubleDown.Double.fallback(Repo.OpenInMemory, Repo.OpenInMemory.new()) ``` Several forms are supported: ``` -------------------------------- ### Repo.Test and Repo.InMemory Opts Handling Source: https://hexdocs.pm/double_down/changelog.md The `Repo.Test` and `Repo.InMemory` modules now handle opts-accepting dispatches by stripping the options and delegating to the base-arity logic, ensuring compatibility with the new opts-accepting variants of `DoubleDown.Repo` operations. ```elixir Repo.Test ``` ```elixir Repo.InMemory ``` -------------------------------- ### Set Up Dynamically-Faked Module Source: https://hexdocs.pm/double_down/DoubleDown.Double.md Use `dynamic/1` to set up a module for dynamic faking, retaining its original implementation as a fallback. Expectations and stubs can then be layered on top. Calls not matching an expect or stub will delegate to the original module. ```elixir SomeClient |> DoubleDown.Double.dynamic() |> DoubleDown.Double.expect(:fetch, fn [_] -> {:error, :timeout} end) ``` -------------------------------- ### Expecting Operations with Dynamic Facades Source: https://hexdocs.pm/double_down/dynamic.md Shows how to use DoubleDown.Double.expect/3 for stubbing specific return values, :passthrough for delegating to the original module, or a stateful function for more complex scenarios. ```elixir DoubleDown.Double.expect(SomeClient, :fetch, fn [_] -> {:error, :timeout} end) # With passthrough — delegates to the original module DoubleDown.Double.expect(SomeClient, :fetch, :passthrough) # Stateful expect (requires a fake) DoubleDown.Double.expect(SomeClient, :fetch, fn [id], state -> {Map.get(state, id), state} end) ``` -------------------------------- ### Retrieve the Original Module Name for Dynamic Dispatch Source: https://hexdocs.pm/double_down/changelog.md Use Dynamic.original_module/1 to get the name of the original module that was backed up before being replaced by a dynamic dispatch shim. ```elixir Dynamic.original_module(MyShimmedModule) ``` -------------------------------- ### Stateless Stub Fallback with Specific Operations Source: https://hexdocs.pm/double_down/testing.md Configures a stateless stub fallback for `DoubleDown.Repo` to handle specific operations like `:get` and `:all` with predefined responses. ```elixir DoubleDown.Double.fallback(DoubleDown.Repo, DoubleDown.Repo.Stub, fn _contract, :get, [User, 1] -> %User{id: 1, name: "Alice"} _contract, :all, [User] -> [%User{id: 1, name: "Alice"}] end ) ``` -------------------------------- ### Wire Up Production Implementations Source: https://hexdocs.pm/double_down/index.html Configures the application to use specific implementations for the DoubleDown Repo and MyApp.Todos.Model in production. ```elixir # config/config.exs config :my_app, DoubleDown.Repo, impl: MyApp.EctoRepo config :my_app, MyApp.Todos.Model, impl: MyApp.Todos.Model.Ecto ``` -------------------------------- ### Use ClosedInMemory Repo with ExMachina Source: https://hexdocs.pm/double_down/changelog.md Demonstrates setting up a closed-world in-memory repository for testing with ExMachina. This allows testing data persistence and retrieval without a database. ```elixir DoubleDown.Double.fake(DoubleDown.Repo, DoubleDown.Repo.ClosedInMemory) insert(:user, name: "Alice", email: "alice@example.com") assert [%User{}] = MyApp.Repo.all(User) assert %User{} = MyApp.Repo.get_by(User, email: "alice@example.com") ``` -------------------------------- ### Multi-Contract Expectations and Stubs Source: https://hexdocs.pm/double_down/testing.md Set expectations on one contract and stub methods on another within the same test setup. This allows for testing interactions between multiple components. ```elixir MyApp.Todos |> DoubleDown.Double.expect(:create_todo, fn [p] -> {:ok, struct!(Todo, p)} end) DoubleDown.Double.stub(DoubleDown.Repo, :one, fn [_] -> nil end) ``` -------------------------------- ### verify_on_exit! Source: https://hexdocs.pm/double_down/DoubleDown.Double.md Registers an `on_exit` callback that verifies expectations after each test. This is useful in `setup` blocks to ensure tests fail if they forget to call `verify!/0` explicitly. ```APIDOC ## verify_on_exit! ### Description Registers an `on_exit` callback that verifies expectations after each test. This ensures that tests which forget to call `verify!/0` explicitly still fail on unconsumed expectations. ### Function Signature ```elixir @spec verify_on_exit!(map()) :: :ok ``` ### Usage Call this in a `setup` block: ```elixir setup :verify_on_exit! ``` Or equivalently: ```elixir setup do DoubleDown.Double.verify_on_exit!() end ``` ### Notes The verification runs in the `on_exit` callback (a separate process), using the test pid captured at setup time. ``` -------------------------------- ### Define ExMachina Factory Source: https://hexdocs.pm/double_down/repo-doubles.md Define your ExMachina factory by pointing it to your Repo facade module. This setup is necessary for integrating ExMachina with Double Down's in-memory repository. ```elixir defmodule MyApp.Factory do use ExMachina.Ecto, repo: MyApp.Repo def user_factory do %MyApp.User{ name: sequence(:name, &"User #{&1}"), email: sequence(:email, &"user#{&1}@example.com"), age: 25 } end end ``` -------------------------------- ### Stateful Fallback with Default State Source: https://hexdocs.pm/double_down/DoubleDown.Double.md Use this to set up a stateful fallback handler with default initial state. Requires a module implementing `DoubleDown.Contract.Dispatch.StatefulHandler`. ```elixir DoubleDown.Double.fallback(MyContract, Repo.OpenInMemory) ``` -------------------------------- ### Fallback to In-Memory Repo or Custom Function Source: https://hexdocs.pm/double_down/dynamic.md After setup, use DoubleDown.Double.fallback/2 to specify a fallback implementation. This can be a module like DoubleDown.Repo.InMemory or a custom function for specific operations. ```elixir # MyApp.EctoRepo is the contract — same module callers use DoubleDown.Double.fallback(MyApp.EctoRepo, DoubleDown.Repo.InMemory) DoubleDown.Double.fallback(SomeThirdPartyClient, fn _contract, :fetch, [id] -> {:ok, id} end) ``` -------------------------------- ### Use Repo.Stub with Double.fallback Source: https://hexdocs.pm/double_down/DoubleDown.Repo.Stub.md Configure DoubleDown.Repo to use Repo.Stub for stateless operations. Writes will succeed, but reads will raise an error. ```elixir DoubleDown.Double.fallback(DoubleDown.Repo, DoubleDown.Repo.Stub) ``` -------------------------------- ### Get log key for a contract Source: https://hexdocs.pm/double_down/DoubleDown.Contract.Dispatch.Keys.md NimbleOwnership key for a contract's dispatch call log. Use this to retrieve the accumulated call log for a specific contract. ```elixir def log_key(module()) :: atom() ``` -------------------------------- ### Module Fallback Stubbing with DoubleDown.Double.stub/3 Source: https://hexdocs.pm/double_down/changelog.md Use `DoubleDown.Double.stub/3` (or `stub/4` with accumulator) to delegate unhandled operations to a module implementing the contract's `@behaviour`. This is validated at `install!` time. ```elixir DoubleDown.Double.stub(MyContract, MyModuleImpl) ``` -------------------------------- ### new Source: https://hexdocs.pm/double_down/DoubleDown.Contract.Dispatch.Passthrough.md Creates a new Passthrough sentinel. ```APIDOC ## `new` ```elixir @spec new() :: t() ``` Create a new Passthrough sentinel. ``` -------------------------------- ### new/2 Callback Source: https://hexdocs.pm/double_down/DoubleDown.Contract.Dispatch.StatefulHandler.md Builds the initial state for a stateful handler from seed data and options. This is called by `Double.fallback/2..4`. ```APIDOC ## `new/2` ### Description Build initial state from seed data and options. Called by `Double.fallback/2..4` to construct the initial state for the stateful handler. ### Parameters * `seed` - seed data (e.g. `%{User => %{1 => %User{}}}` for Repo.OpenInMemory) * `opts` - additional options (e.g. `fallback_fn: fn ... end`) ### Callback Signature ```elixir @callback new(seed :: term(), opts :: keyword()) :: term() ``` ``` -------------------------------- ### Explicit Passthrough to Original Module Source: https://hexdocs.pm/double_down/dynamic.md When a handler is installed, Double.passthrough() and :passthrough expects delegate to the fallback, not the original module. Use Double.dynamic/1 to explicitly delegate to the original module. ```elixir SomeClient |> DoubleDown.Double.dynamic() |> DoubleDown.Double.expect(:fetch, :passthrough) ``` -------------------------------- ### new/1 Source: https://hexdocs.pm/double_down/DoubleDown.Contract.Dispatch.HandlerMeta.Module.md Creates a new Module handler meta. It validates that the provided implementation is an atom. ```APIDOC ## `new/1` ### Description Create a new Module handler meta. Validates that `impl` is an atom. ### Function Signature ```elixir new(module()) :: t() ``` ### Parameters #### Path Parameters - **impl** (module) - Required - The module to be used as the handler implementation. ### Returns - **t()** - A new instance of `DoubleDown.Contract.Dispatch.HandlerMeta.Module`. ```