Packages
double_down
0.40.0
0.69.0
0.68.0
0.66.0
0.65.0
0.64.1
0.64.0
0.63.3
0.63.2
0.63.1
0.63.0
0.62.1
0.61.0
0.60.4
0.60.3
0.60.2
0.60.1
0.60.0
0.59.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.0
0.54.0
0.53.0
0.52.3
0.52.2
0.52.1
0.52.0
0.51.0
0.50.1
0.50.0
0.49.0
0.48.1
0.48.0
0.47.2
0.47.1
0.47.0
0.46.3
0.46.2
0.46.1
0.46.0
0.45.0
0.44.0
0.43.0
0.42.0
0.41.1
0.41.0
0.40.0
0.39.0
0.38.0
0.37.2
0.37.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.1
0.31.0
0.30.1
0.30.0
0.29.0
0.28.1
0.28.0
0.27.0
0.26.0
0.24.0
Builds on the Mox pattern — generates behaviours and dispatch facades from `defcallback` declarations — and adds stateful test doubles powerful enough to test Ecto.Repo operations without a database.
Current section
Files
Jump to
Current section
Files
docs/process-sharing.md
# Process Sharing
[< Logging](logging.md) | [Up: README](../README.md) | [Repo >](repo.md)
All test doubles are process-scoped. `async: true` tests run in full
isolation — each test process has its own doubles, state, and logs.
## Task.async children
**Task.async children** automatically inherit their parent's doubles
via the `$callers` chain. No setup needed.
## Explicit sharing with `allow`
**Other processes** (plain `spawn`, Agent, GenServer) need explicit
sharing:
```elixir
DoubleDown.Double.allow(MyApp.Todos, self(), agent_pid)
```
`allow/3` also accepts a lazy pid function for processes that don't
exist yet at setup time:
```elixir
DoubleDown.Double.allow(MyApp.Todos, fn -> GenServer.whereis(MyWorker) end)
```
## Global mode
For integration-style tests involving supervision trees, named
GenServers, Broadway pipelines, or Oban workers — where individual
process pids are not easily accessible — you can switch to global
mode:
```elixir
setup do
DoubleDown.Testing.set_mode_to_global()
DoubleDown.Double.fake(DoubleDown.Repo, DoubleDown.Repo.InMemory)
on_exit(fn -> DoubleDown.Testing.set_mode_to_private() end)
:ok
end
```
In global mode, all doubles registered by the test process are
visible to every process in the VM without explicit `allow/3` calls.
**Warning:** Global mode is incompatible with `async: true`. When
active, all tests share the same doubles, so concurrent tests will
interfere with each other. Only use global mode in tests with
`async: false`. Call `set_mode_to_private/0` in `on_exit` to restore
per-process isolation for subsequent tests.
## Choosing the right approach
| Situation | Approach | `async: true`? |
|-----------|----------|----------------|
| Direct function calls | No extra setup needed | Yes |
| `Task.async` / `Task.Supervisor` | Automatic via `$callers` | Yes |
| Known pid (Agent, named GenServer) | `allow/3` with the pid | Yes |
| Pid not known at setup time | `allow/3` with lazy fn | Yes |
| Supervision tree / Broadway / Oban | `set_mode_to_global/0` | **No** |
## Example: testing a GenServer that dispatches through a contract
```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
```
## Example: testing through a supervision tree
When you can't easily get pids for every process in the tree, use
global mode:
```elixir
defmodule MyApp.PipelineIntegrationTest do
use ExUnit.Case, async: false
setup do
DoubleDown.Testing.set_mode_to_global()
DoubleDown.Double.fake(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
```
## Cleanup
Call `reset/0` to clear all doubles, state, and logs for the current
process:
```elixir
setup do
DoubleDown.Testing.reset()
# ... set up fresh doubles ...
end
```
In practice, most tests just set doubles in `setup` without calling
`reset` — NimbleOwnership's per-process isolation means there's no
cross-test leakage.
---
[< Logging](logging.md) | [Up: README](../README.md) | [Repo >](repo.md)