Packages
double_down
0.26.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
double_down
CHANGELOG.md
CHANGELOG.md
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.26.0]
### Changed
- **Breaking:** `DoubleDown.Handler` renamed to `DoubleDown.Double`.
`stub` for module and stateful fallbacks split out into `fake`:
- `Double.stub(contract, :op, fun)` — per-operation stub (canned value)
- `Double.stub(contract, fun)` — 2-arity function fallback
- `Double.fake(contract, module)` — module fake
- `Double.fake(contract, fun, init_state)` — stateful fake
- **Breaking:** `DoubleDown.Log` API simplified — `match` and `reject`
no longer take a contract parameter. The contract is specified once
at `verify!` time. `verify!` now returns `{:ok, log}` on success.
- Handler error messages now include the contract name and args.
- `.formatter.exs` updated for `defcallback` rename.
### Added
- `DoubleDown.Log.verify!` returns `{:ok, log}` on success and
includes the full dispatch log in all error messages — useful for
REPL debugging.
- `:static_dispatch?` option on `use DoubleDown.Facade` — resolves
the implementation module at compile time and generates direct
function calls, eliminating `Application.get_env` overhead entirely.
Defaults to `fn -> Mix.env() == :prod end`.
- Comprehensive docs review: restructured testing.md with `Double` as
primary API, updated all examples to use `Double.expect`/`stub`/`fake`
instead of raw `set_*_handler` APIs, consistent terminology throughout.
## [0.25.0]
### Changed
- **Breaking:** `defport` renamed to `defcallback`, `__port_operations__/0`
renamed to `__callbacks__/0`. The `defcallback` macro uses the same
syntax as `@callback` — replace the keyword and you're done.
- **Breaking:** `DoubleDown.Repo.Contract` renamed to `DoubleDown.Repo`.
Less verbose in `Handler.stub` and `Handler.expect` calls.
- **Breaking:** `DoubleDown.Log` API simplified — `match` and `reject`
no longer take a contract parameter. The contract is specified once
at `verify!` time: `Log.match(:op, fn _ -> true end) |> Log.verify!(MyContract)`.
### Added
- `:static_dispatch?` option on `use DoubleDown.Facade` — resolves
the implementation module at compile time and generates direct
function calls, eliminating `Application.get_env` overhead entirely.
Defaults to `fn -> Mix.env() == :prod end`. Falls back to runtime
config dispatch when compile-time config is unavailable.
- README rewritten with new "Why DoubleDown?" section, Mox comparison,
failure scenario example, and implementation snippet.
- Comprehensive docs review: "port" → "contract" throughout,
terminology updated, fail-fast pattern documented, Skuld references
simplified, LSP docs bullet added to `defcallback` rationale.
## [0.24.0]
### Changed
- **Breaking:** Library renamed from `hex_port` / `HexPort` to
`double_down` / `DoubleDown`. All module names, app name, package
name, and GitHub URLs updated. The emphasis has shifted from
hexagonal architecture boundaries to the distinctive test double
capabilities.
## [0.23.0]
### Changed
- **Breaking:** `DoubleDown.Double` API simplified — `expect` and `stub`
now write directly to NimbleOwnership with immediate effect. Removed
`%DoubleDown.Double{}` struct, `new/0`, and `install!/1`. All functions
return the contract module atom for Mimic-style piping:
MyContract
|> DoubleDown.Double.stub(MyImpl)
|> DoubleDown.Double.expect(:get, fn [id] -> %Thing{id: id} end)
A canonical handler function is installed on first touch and reads
dispatch config from state — no builder assembly step needed.
## [0.22.0]
### Added
- `DoubleDown.Double.expect/4..5` now accepts `:passthrough` as the
handler argument. A `:passthrough` expect delegates to the
configured fallback (fn, stateful, or module) while consuming the
expect for `verify!` counting. Supports `times: n`. Enables
call-counting without changing behaviour, and can be mixed with
function expects for patterns like "first insert succeeds through
InMemory, second fails".
- Documentation in `docs/repo.md` for using `DoubleDown.Double` with
`Repo.Test` and `Repo.InMemory` for failure scenario testing,
including error simulation, `:passthrough` call counting, and
combined Handler + Log assertions.
### Fixed
- Added `@spec` clauses for all `stub/2..4` forms to satisfy
Dialyzer.
## [0.21.0]
### Added
- `DoubleDown.Double.stub/3` (with accumulator: `stub/4`) for module
fallback — delegates unhandled operations to a module implementing
the contract's `@behaviour`. Validated at `install!` time.
- `DoubleDown.Double.stub/3` (with accumulator: `stub/4`) for stateful
fallback — accepts a 3-arity `fn operation, args, state ->
{result, new_state} end` with initial state, same signature as
`set_stateful_handler`. Integrates stateful fakes (e.g.
`Repo.InMemory`) into the Handler dispatch chain. Expects that
short-circuit (e.g. error simulation) leave the fallback state
unchanged.
- Fallback types are now a tagged union (`{:fn, fun}`,
`{:stateful, fun, init_state}`, `{:module, module}`) — mutually
exclusive, setting one replaces the other.
## [0.20.0]
### Added
- `DoubleDown.Double.verify_on_exit!/0` — registers an `on_exit`
callback that automatically verifies all expectations after each
test. Usable as `setup :verify_on_exit!`. Uses
`NimbleOwnership.set_owner_to_manual_cleanup/2` to preserve
ownership data until the on_exit callback runs.
- `DoubleDown.Double.verify!/1` — verifies expectations for a
specific process pid, used internally by `verify_on_exit!/0`.
### Fixed
- Added `:ex_unit` to `plt_add_apps` in `mix.exs` so Dialyzer can
resolve the `ExUnit.Callbacks.on_exit/2` call in
`DoubleDown.Double.verify_on_exit!/0`.
## [0.19.0]
### Added
- `DoubleDown.Double.stub/2` and `stub/3` (with accumulator) for
2-arity contract-wide fallback stubs. Accepts
`fn operation, args -> result end` — the same signature as
`set_fn_handler` — as a catch-all for operations without a
specific expect or per-operation stub. Dispatch priority:
expects > per-operation stubs > fallback stub > raise.
## [0.18.0]
### Added
- `DoubleDown.Double` — Mox-style expect/stub handler builder. Builds
stateful handler functions from a declarative specification with
multi-contract chaining and ordered expectations. API:
`expect/3..5`, `stub/3..4`, `install!/1`, `verify!/0`.
- `DoubleDown.Log` — log-based expectation matcher. Declares structured
expectations against the dispatch log after execution, matching on
the full `{contract, operation, args, result}` tuple. Supports
loose (default) and strict matching modes, `times: n` counting,
and `reject` expectations. API: `match/3..5`, `reject/2..3`,
`verify!/1..2`.
- Terminology mapping and glossary in README and getting-started
guide, mapping DoubleDown concepts (contract, facade, test double,
port) to familiar Elixir/Mox equivalents with a stub/mock/fake
breakdown.
## [0.17.0]
### Changed
- **Breaking:** Renamed generated key helper from `key/N` to `__key__/N`
on facade modules, following the Elixir convention for generated
introspection functions. This avoids clashes with user-defined
`defcallback key(...)` operations.
### Fixed
- Added `:mix` to `plt_add_apps` in `mix.exs` so Dialyzer can resolve
the compile-time `Mix.env/0` call in `DoubleDown.Facade.__using__/1`.
## [0.16.1]
### Fixed
- Added `:mix` to `plt_add_apps` in `mix.exs` so Dialyzer can resolve
the compile-time `Mix.env/0` call in `DoubleDown.Facade.__using__/1`.
### Changed
- Documentation updates for `:test_dispatch?` in `docs/getting-started.md`
(dispatch resolution section) and `docs/testing.md` (setup section).
## [0.16.0]
### Added
- `:test_dispatch?` option for `use DoubleDown.Facade` — controls whether
the generated facade includes the `NimbleOwnership`-based test handler
resolution step. Accepts `true`, `false`, or a zero-arity function
returning a boolean, evaluated at compile time. Defaults to
`fn -> Mix.env() != :prod end`, so production builds get a config-only
dispatch path with zero `NimbleOwnership` overhead (no
`GenServer.whereis` ETS lookup).
- `DoubleDown.Dispatch.call_config/4` — config-only dispatch function that
skips test handler resolution entirely. Used by facades compiled with
`test_dispatch?: false`.
## [0.15.0]
### Added
- `pre_dispatch` option for `defcallback` — a generic mechanism for
transforming arguments before dispatch. Accepts a function
`(args, facade_module) -> args` declared at the contract level,
spliced into the generated facade function as AST.
- `Repo.Test` tests split into dedicated `test/double_down/repo/test_test.exs`
module.
### Changed
- 1-arity `transact` functions are now wrapped into 0-arity thunks
at the facade boundary via `pre_dispatch`. The thunk closes over
the facade module, so calls inside the function (e.g.
`repo.insert(cs)`) go through the facade dispatch chain. This
ensures facade-level concerns (logging, telemetry) apply in both
test and production.
- `Repo.Test` and `Repo.InMemory` adapters no longer handle 1-arity
transaction functions — they always receive 0-arity thunks (from
`pre_dispatch` wrapping) or `Ecto.Multi` structs.
- The hardcoded `:transact` special-case in `DoubleDown.Facade` has been
removed. The Repo-specific facade injection is now declared on the
`defcallback` in `DoubleDown.Repo` using the generic
`pre_dispatch` mechanism.
### Fixed
- User-supplied fallback functions in `Repo.InMemory` that raise
non-`FunctionClauseError` exceptions (e.g. `RuntimeError`,
`ArgumentError`) no longer crash the NimbleOwnership GenServer.
Exceptions are captured and re-raised in the calling test process
via `{:defer, fn -> reraise ... end}`.
## [0.14.0]
### Added
- `DoubleDown.Repo.insert_all/3` — standalone bulk insert
operation, dispatched via fallback in both test adapters.
- `DoubleDown.Testing.set_mode_to_global/0` and `set_mode_to_private/0`
— global handler mode for testing through supervision trees,
Broadway pipelines, and other process trees where individual pids
are not accessible. Uses NimbleOwnership shared mode. Incompatible
with `async: true`.
- `DoubleDown.Repo.Autogenerate` — shared helper module for
autogenerating primary keys and timestamps in test adapters.
Handles `:id` (integer auto-increment), `:binary_id` (UUID),
parameterized types (`Ecto.UUID`, `Uniq.UUID`, etc.), and
`@primary_key false` schemas.
- `docs/migration.md` — incremental adoption guide covering the
two-contract pattern, coexisting with direct Ecto.Repo calls, and
the fail-fast test config pattern.
- Process-testing patterns in `docs/testing.md` — decision table,
GenServer example, supervision tree example.
### Changed
- Test adapters (`Repo.Test`, `Repo.InMemory`) now check
`changeset.valid?` before applying changes — invalid changesets
return `{:error, changeset}`, matching real Ecto.Repo behaviour.
- Test adapters now populate `inserted_at`/`updated_at` timestamps
via Ecto's `__schema__(:autogenerate)` metadata. Custom field
names and timestamp types are handled automatically.
- 1-arity `transact` functions now receive the facade module instead
of `nil`, enabling `fn repo -> repo.insert(cs) end` patterns.
- The internal opts key for threading the facade module through
transact was renamed from `:repo_facade` to `DoubleDown.Repo.Facade`
for proper namespacing.
- Primary key autogeneration is now metadata-driven — supports
`:binary_id` (UUID), `Ecto.UUID`, and other parameterized types.
Raises `ArgumentError` when autogeneration is not configured and
no PK value is provided.
- Autogeneration logic extracted from `Repo.Test` and
`Repo.InMemory` into shared `DoubleDown.Repo.Autogenerate` module.
- Repo contract now has 16 operations (was 15).
### Fixed
- Invalid changesets passed to `Repo.Test` or `Repo.InMemory`
`insert`/`update` no longer silently succeed — they return
`{:error, changeset}`.
- `Repo.InMemory` store is unchanged after a failed insert/update
with an invalid changeset.
## [0.13.0]
### Added
- Fail-fast documentation for `impl: nil` test configuration.
### Changed
- Improved error messages when no implementation is configured in
test mode.
## [0.12.0]
### Changed
- Removed unused Ecto wrapper macro.
- Version now read from `VERSION` file.
## [0.11.1]
### Changed
- Documentation improvements (README, hexdocs, testing guide).
- Removed unnecessary `reset` calls from test examples.
## [0.11.0]
### Fixed
- Fixed compiler warnings.
## [0.10.0]
### Added
- `Facade` without implicit `Contract` — `use DoubleDown.Facade` with
an explicit `:contract` option for separate contract modules.
- Documentation explaining why `defcallback` is used instead of standard
`@callback` declarations.
## [0.9.0]
### Added
- Single-module `Contract + Facade` — `use DoubleDown.Facade` without
a `:contract` option implicitly sets up the contract in the same
module.
### Changed
- Dispatch references the contract module, not the facade.
## [0.8.0]
### Added
- `DoubleDown.Repo` — built-in 15-operation Ecto Repo
contract with `Repo.Test` (stateless) and `Repo.InMemory`
(stateful) test doubles.
- `MultiStepper` for stepping through `Ecto.Multi` operations
without a database.
### Changed
- Renamed `Port` to `Facade` throughout.
- Removed separate `.Behaviour` module — behaviours are generated
directly on the contract module.
## [0.7.0]
### Changed
- `Repo.InMemory` fallback function now receives state as a third
argument `(operation, args, state)`, enabling fallbacks that
compose canned data with records inserted during the test.
## [0.6.0]
### Fixed
- Made `DoubleDown.Contract.__using__/1` idempotent — safe to `use`
multiple times.
## [0.5.0]
### Changed
- Improved `Repo.Test` stateless handler.
## [0.4.0]
### Added
- `Repo.InMemory` — stateful in-memory Repo implementation with
read-after-write consistency for PK-based lookups.
- NimbleOwnership-based process-scoped handler isolation for
`async: true` tests.
## [0.3.1]
### Fixed
- Expand type aliases at macro time in `defcallback` to resolve
Dialyzer `unknown_type` errors.
## [0.3.0]
### Added
- `transact` defcallback with `{:defer, fn}` support for stateful
dispatch — avoids NimbleOwnership deadlocks.
- `Repo.transact!` for `Ecto.Multi` operations.
## [0.2.0]
### Changed
- Split `DoubleDown` into `DoubleDown.Contract` and `DoubleDown.Port`
(later renamed to `Facade`).
## [0.1.0]
### Added
- Initial release — `defcallback` macro, `DoubleDown.Contract`,
`DoubleDown.Testing` with NimbleOwnership, `Repo.Test` stateless
adapter, CI setup, Credo, Dialyzer.
[Unreleased]: https://github.com/mccraigmccraig/double_down/compare/v0.26.0...HEAD
[0.26.0]: https://github.com/mccraigmccraig/double_down/compare/v0.25.0...v0.26.0
[0.25.0]: https://github.com/mccraigmccraig/double_down/compare/v0.24.0...v0.25.0
[0.24.0]: https://github.com/mccraigmccraig/double_down/compare/v0.23.0...v0.24.0
[0.23.0]: https://github.com/mccraigmccraig/double_down/compare/v0.22.0...v0.23.0
[0.22.0]: https://github.com/mccraigmccraig/double_down/compare/v0.21.0...v0.22.0
[0.21.0]: https://github.com/mccraigmccraig/double_down/compare/v0.20.0...v0.21.0
[0.20.0]: https://github.com/mccraigmccraig/double_down/compare/v0.19.0...v0.20.0
[0.19.0]: https://github.com/mccraigmccraig/double_down/compare/v0.18.0...v0.19.0
[0.18.0]: https://github.com/mccraigmccraig/double_down/compare/v0.17.0...v0.18.0
[0.17.0]: https://github.com/mccraigmccraig/double_down/compare/v0.16.1...v0.17.0
[0.16.1]: https://github.com/mccraigmccraig/double_down/compare/v0.16.0...v0.16.1
[0.16.0]: https://github.com/mccraigmccraig/double_down/compare/v0.15.0...v0.16.0
[0.15.0]: https://github.com/mccraigmccraig/double_down/compare/v0.14.0...v0.15.0
[0.14.0]: https://github.com/mccraigmccraig/double_down/compare/v0.13.0...v0.14.0
[0.13.0]: https://github.com/mccraigmccraig/double_down/compare/v0.12.0...v0.13.0
[0.12.0]: https://github.com/mccraigmccraig/double_down/compare/v0.11.1...v0.12.0
[0.11.1]: https://github.com/mccraigmccraig/double_down/compare/v0.11.0...v0.11.1
[0.11.0]: https://github.com/mccraigmccraig/double_down/compare/v0.10.0...v0.11.0
[0.10.0]: https://github.com/mccraigmccraig/double_down/compare/v0.9.0...v0.10.0
[0.9.0]: https://github.com/mccraigmccraig/double_down/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/mccraigmccraig/double_down/compare/v0.7.0...v0.8.0
[0.7.0]: https://github.com/mccraigmccraig/double_down/compare/v0.6.0...v0.7.0
[0.6.0]: https://github.com/mccraigmccraig/double_down/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/mccraigmccraig/double_down/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/mccraigmccraig/double_down/compare/v0.3.1...v0.4.0
[0.3.1]: https://github.com/mccraigmccraig/double_down/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/mccraigmccraig/double_down/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/mccraigmccraig/double_down/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/mccraigmccraig/double_down/releases/tag/v0.1.0