Per-test configuration isolation for ExUnit.

Provides put_config/3, get_config/2, and diff/1 to manage test-scoped application config that intercepts Application.get_env transparently.

Setup

In test/test_helper.exs:

Mace.Store.init()
ExUnit.start()

Modules that need Application.get_env interception must also call Mace.Mock.install() in a setup_all block.

In your test module:

defmodule MyTest do
  use ExUnit.Case, async: true

  setup do
    Mace.put_config(:my_app, :timeout, 100)
    :ok
  end

  test "uses overridden timeout" do
    # Application.get_env(:my_app, :timeout) => 100
    assert MyModule.do_thing() == :ok
  end
end

Summary

Functions

Records the current process's config diffs and then resets. Optional — replace a bare Mace.reset/0 in on_exit to enable automatic config-diff display on test failure.

Same as get/2 but logs the full tree walk path to stderr. Use to diagnose why a test isn't seeing expected config.

Removes a specific config override for the current process.

Returns a formatted diff string comparing the current process's config overrides against the application defaults.

get(app, key) deprecated

Gets the active config override for the current process. Returns {:ok, value} or :error.

Returns the current pid's full config overrides as a nested map. Useful for debugging and for manual config transfer to spawned processes.

Sets multiple config overrides from a keyword list.

Sets a config override for the current test process.

Clears all config overrides for the current test process. Normally unnecessary — cleanup happens automatically when the test process exits via the DOWN handler. Use as an escape hatch when you need to explicitly clear config mid-test.

set(app, kvlist) deprecated

Spawns a Task that inherits the current process's config overrides. Use instead of Task.async/1 when the spawned code calls Application.get_env in scenarios where the spawned task isn't linked or monitored by a test. This should be a very rare occurrence.

Types

app()

@type app() :: atom()

key()

@type key() :: atom()

kvlist()

@type kvlist() :: %{required(atom()) => term()} | Keyword.t() | MapSet.t()

value()

@type value() :: term()

Functions

cleanup(context)

@spec cleanup(map()) :: :ok

Records the current process's config diffs and then resets. Optional — replace a bare Mace.reset/0 in on_exit to enable automatic config-diff display on test failure.

Example

setup context do
  Mace.put_config(:my_app, :timeout, 100)
  on_exit(fn -> Mace.cleanup(context) end)
  :ok
end

debug_get(app, key)

@spec debug_get(app(), key()) :: {:ok, value()} | :error

Same as get/2 but logs the full tree walk path to stderr. Use to diagnose why a test isn't seeing expected config.

delete(app, key)

@spec delete(app(), key()) :: :ok

Removes a specific config override for the current process.

Sets the key to nil, mirroring Application.delete_env/2. Subsequent Mace.get_config/2 calls return {:ok, nil}.

Examples

iex> Mace.put_config(:my_app, :timeout, 100)
iex> Mace.delete(:my_app, :timeout)
:ok
iex> Mace.get_config(:my_app, :timeout)
{:ok, nil}

diff(app)

@spec diff(app()) :: String.t()

Returns a formatted diff string comparing the current process's config overrides against the application defaults.

Returns empty string if no overrides or all overrides match defaults.

Example

iex> Mace.put_config(:my_app, debug: true)
iex> Mace.diff(:my_app)
"""
Test config diff for :my_app:
──────────────────────────────────────────────────
  :debug:  <not set> (default)  →  true (test)
──────────────────────────────────────────────────
"""

get(app, key)

This function is deprecated. Use Mace.get_config/2 instead.

get_config(app, key)

@spec get_config(app(), key()) :: {:ok, value()} | :error

Gets the active config override for the current process. Returns {:ok, value} or :error.

Example

iex> Mace.put_config(:my_app, debug: true)
iex> Mace.get_config(:my_app, :debug)
{:ok, true}

pid_config()

@spec pid_config() :: %{required(app()) => %{required(key()) => value()}}

Returns the current pid's full config overrides as a nested map. Useful for debugging and for manual config transfer to spawned processes.

Example

iex> Mace.put_config(:my_app, timeout: 100, debug: true)
iex> Mace.pid_config()
%{my_app: %{timeout: 100, debug: true}}

put_config(app, kvlist)

@spec put_config(app(), kvlist()) :: [:ok]

Sets multiple config overrides from a keyword list.

Example

iex> Mace.put_config(:my_app, timeout: 100, debug: true)
[:ok, :ok]

put_config(app, key, value)

@spec put_config(app(), key(), value()) :: :ok

Sets a config override for the current test process.

Subsequent calls to Application.get_env(app, key) from the same process will return value instead of the real application config.

Example

iex> Mace.put_config(:my_app, :debug, true)
:ok

reset()

@spec reset() :: :ok

Clears all config overrides for the current test process. Normally unnecessary — cleanup happens automatically when the test process exits via the DOWN handler. Use as an escape hatch when you need to explicitly clear config mid-test.

set(app, kvlist)

This function is deprecated. Use Mace.put_config/2 instead.

set(app, key, value)

This function is deprecated. Use Mace.put_config/3 instead.

task(fun)

@spec task((-> any())) :: Task.t()

Spawns a Task that inherits the current process's config overrides. Use instead of Task.async/1 when the spawned code calls Application.get_env in scenarios where the spawned task isn't linked or monitored by a test. This should be a very rare occurrence.

Example

iex> task = Mace.task(fn -> :ok end)
iex> Task.await(task)
:ok