DoubleDown
DoubleDown is a test-double library for Elixir: mocks and fakes. It has
multiple routes to adding test boundaries to your system, and it goes beyond
mocks with stateful test doubles aka fakes. It includes an Ecto.Repo fake
powerful enough to run ExMachina factories without a database.
Tests that exercise database features — constraints, complex queries,
migrations — should continue to run against a real database. But unit tests
that use the database merely as an easy but slow way of getting data into
the right place can run without one. DoubleDown's InMemory Repo lets you
keep the factory and drop the DB.
How it works
A function call passes through four layers:
┌──────────────────────────────────────────────────────┐
│ FUNCTION CALL │
│ MyApp.Repo.insert(changeset) │
└──────────────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ 1. CONTRACT │
│ (type-level interface) │
│ │
│ ┌───────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ defcallback │ │ @behaviour │ │ any module │ │
│ │ (explicit) │ │ (explicit) │ │ (implicit) │ │
│ └────────┬──────┘ └──────┬───────┘ └──────┬──────┘ │
└───────────┼───────────────┼────────────────┼─────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────────┐
│ 2. FACADE │
│ (generated dispatch functions) │
│ │
│ ┌───────────────┐ ┌────────────────┐ ┌─────────────┐ │
│ │ContractFacade │ │BehaviourFacadde│ │DynamicFacade│ │
│ └────────┬──────┘ └────────┬───────┘ └──────┬──────┘ │
└──────────┼─────────────────┼────────────────┼────────┘
└─────────────────┼────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ 3. DISPATCH │
│ (call resolution) │
│ │
│ ┌────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Static │ │ Runtime │ │ Test │ │
│ │ (compile- │ │ Config │ │ Handler │ │
│ │ time, │ │ │ │ │ │
│ │ zero │ │ App.get_env │ │ NimbleOwner- │ │
│ │ overhead) │ │ → apply/3 │ │ ship lookup │ │
│ └─────┬──────┘ └──────┬───────┘ └──────┬───────┘ │
└────────┼────────────────┼─────────────────┼──────────┘
└────────────────┼─────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ 4. IMPLEMENTATION │
│ (actual execution) │
│ │
│ ┌──────────────────────────┐ ┌──────────────────┐ │
│ │ Production Module │ │ Test Double │ │
│ │ (@behaviour Contract) │ │ (stub / fake / │ │
│ │ │ │ expect) │ │
│ └──────────────────────────┘ └──────────────────┘ │
└──────────────────────────────────────────────────────┘Three facade types let you add boundaries at different levels of control:
| Facade | Contract | Best for |
|---|---|---|
ContractFacade | defcallback (explicit, typed) | New code you control |
BehaviourFacade |
existing @behaviour | Third-party or legacy behaviours |
DynamicFacade | implicit (module's public API) | Adding boundaries without touching source |
Dispatch is uniform across all three — the same resolution mechanism, the same
DoubleDown.Double API for tests, the same DoubleDown.Log for tracing calls.
In production, dispatch can be eliminated entirely at compile time (static
dispatch inlines direct calls — zero overhead versus calling the implementation
directly).
Installation
Add double_down to your dependencies in mix.exs:
def deps do
[
{:double_down, "~> 0.48"}
]
endDocumentation
- Boundaries — contracts, facades, and the dispatch mechanism
- Dispatch — uniform dispatch resolution
- Stateful Doubles — stateful fakes, handler state, cross-contract access
- Double API — expect, stub, fallback, verify!, passthrough
- Repo — the built-in Ecto.Repo contract and its test doubles
Archived documentation from previous versions lives in docs/archive/ — these cover the same concepts but may use outdated module names.
License
MIT License — see LICENSE for details.