< Dynamic Facades | Up: README | Process Sharing >

Dispatch logging

Record every call that crosses a contract boundary, then assert on the sequence:

setup do
  MyApp.Todos
  |> DoubleDown.Double.stub(:get_todo, fn [id] -> {:ok, %Todo{id: id}} end)

  DoubleDown.Testing.enable_log(MyApp.Todos)
  :ok
end

test "logs dispatch calls" do
  MyApp.Todos.get_todo("42")

  assert [{MyApp.Todos, :get_todo, ["42"], {:ok, %Todo{id: "42"}}}] =
    DoubleDown.Testing.get_log(MyApp.Todos)
end

The log captures {contract, operation, args, result} tuples in dispatch order. Enable logging before making calls; get_log/1 returns the full sequence.

Log matcher (structured log assertions)

DoubleDown.Log provides structured expectations against the dispatch log. Unlike get_log/1 + manual assertions, it supports ordered matching, counting, reject expectations, and strict mode.

This is particularly valuable with fakes like Repo.InMemory that do real computation (changeset validation, PK autogeneration, timestamps) — matching on results in the log is a meaningful assertion, not a tautology.

Basic usage

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)

Matcher functions only need positive clauses — FunctionClauseError is caught and treated as "didn't match". No _ -> false catch-all needed, though returning false explicitly can be useful for excluding specific values that are hard to exclude with pattern matching alone.

Counting occurrences

DoubleDown.Log.match(:insert, fn
  {_, _, [%Changeset{data: %Discrepancy{}}], {:ok, _}} -> true
end, times: 3)
|> DoubleDown.Log.verify!(DoubleDown.Repo)

Strict mode

By default, extra log entries between matchers are ignored (loose mode). Strict mode requires every log entry to be matched:

DoubleDown.Log.match(:insert, fn _ -> true end)
|> DoubleDown.Log.match(:update, fn _ -> true end)
|> DoubleDown.Log.verify!(MyContract, strict: true)

Using with DoubleDown.Double

Double and Log serve complementary roles — Double for fail-fast validation and producing return values, Log for after-the-fact result inspection:

# 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)

< Dynamic Facades | Up: README | Process Sharing >