Mox for Elixir: explicit contracts and concurrent mocks
Mocks and explicit contracts in Elixir
At a glance
- What is it?
- Mox is an Elixir testing library for defining mocks from application behaviours and using them in concurrent tests. It favors explicit contracts, stable module setup, and assertions written with normal Elixir functions.
- Who is it for?
- Adopt Mox when Elixir tests need behaviour-backed mocks that remain compatible with `async: true` and explicit call assertions. Do not choose it for ad-hoc substitutes, per-test dynamic modules, or automatic enforcement of callback typespecs.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Elixir, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Star history
- dashbitco/mox1,402 ★ · +1
| Repository | 2026-10-16 | 2026-10-24 |
|---|---|---|
| dashbitco/mox | 1.4k | 1.4k |
1 Stars in the last 8 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Why Mox limits substitutes to declared behaviours
Mox solves a specific Elixir testing problem: replacing an implementation while retaining the contract declared by an application behaviour. The README states that Mox has "no ad-hoc mocks" and allows a mock to be created only from a behaviour. This restriction puts the contract in a callback definition before the test starts, rather than allowing a test to invent an unrelated object with any methods that happen to satisfy the current assertion.
The intended users are Elixir developers who divide application code around `@callback` definitions and need tests that remain compatible with ExUnit concurrency. Typical boundaries include external API clients, storage adapters, notification senders, and other components whose production implementation can be replaced without changing the calling module. The example uses weather data, but the mechanism applies to any dependency already represented by a behaviour.
This design has a cost. A team must first define a useful behaviour. Mox will not remove that design work or turn an undefined contract into a stable one. It is therefore a poor fit for codebases that mock arbitrary modules without declared callbacks. Its preference for concurrency also makes static setup more important, since a test should not need to generate a fresh module for each case.
How Mox routes a call through configuration
The README's example shows a concrete data path. `WeatherBehaviour` declares `get_weather/1`, `WeatherImpl` implements that behaviour, and `Bound.get_weather/1` delegates to whichever implementation is selected by application configuration. Its private `weather_impl/0` function reads the `:weather` value for the `:bound` application and falls back to `WeatherImpl`. Test setup replaces that default with `WeatherBehaviourMock`, so calling the higher-level API reaches the mock rather than the HTTP-based implementation.
The mock comes from `Mox.defmock/2`. It exists outside an individual test case, matching Mox's stated preference for defining mocks in `test/test_helper.exs` or a `setup_all` block. A test imports Mox and uses `expect/3` to attach a function to the expected mock call. When `Bound.get_weather("Chicago")` runs, the mock function receives the city, checks that it equals `"Chicago"`, and returns a successful map.
Mox does not document a global interception mechanism that transparently rewrites arbitrary calls. Instead, the example requires a configurable implementation location in application code. That indirection is useful for testability, but adding Mox does not automatically make an existing component replaceable. The surrounding module must expose or internally select an implementation.
Installing Mox and configuring the first use
Mox is distributed as an Elixir package and is added as a test-only dependency in `mix.exs`. The README uses the `1.0` requirement. Mox starts automatically unless the project overrides application inference with an `:applications` key. In that case, the README directs developers to use `:extra_applications` instead or explicitly start Mox from `test/test_helper.exs`.
The following is the documented dependency declaration. Add it inside the dependency list in `mix.exs`:
def deps do
[
{:mox, "~> 1.0", only: :test}
]
endAfter dependency resolution through Mix, define the behaviour in application code. The example contract accepts a binary city name and returns either a map or an error reason:
defmodule WeatherBehaviour do
@callback get_weather(binary()) :: {:ok, map()} | {:error, binary()}
endThe higher-level module needs a configuration-based implementation selector. This is the point where test setup can switch from the production implementation to the mock.
Defining the shared mock and selecting it in tests
Mox recommends defining the mock once, preferably in `test/test_helper.exs`, rather than creating modules dynamically for each test. The README example changes the `:bound` application's `:weather` setting from its production default to the generated mock. `ExUnit.start()` is then called in the same helper.
The documented setup is:
Mox.defmock(WeatherBehaviourMock, for: WeatherBehaviour)
Application.put_env(:bound, :weather, WeatherBehaviourMock)
ExUnit.start()The caller can then read the configured implementation with `Application.get_env/3`. In the README's `Bound` example, `weather_impl()` returns the configured module and calls `get_weather/1` on it. This creates a narrow substitution point rather than changing the behaviour callback or rewriting the higher-level call site.
The setup is global to the test application configuration, so teams should keep this change in test setup rather than production configuration. The README does not describe automatic cleanup for the environment setting, so the example does not claim that Mox restores it automatically.
Writing a first concurrent-friendly expectation
The test uses `setup :verify_on_exit!`, declares an `expect/3` function, and invokes the higher-level `Bound` API. Mox's recommended assertion style is ordinary Elixir code inside the expected function. Here, `assert args == "Chicago"` verifies the input, while the function's return value determines the result seen by `Bound.get_weather/1`.
The complete example test is:
defmodule BoundTest do
use ExUnit.Case
import Mox
setup :verify_on_exit!
describe "get_weather/1" do
test "fetches weather based on a location" do
expect(WeatherBehaviourMock, :get_weather, fn args ->
assert args == "Chicago"
{:ok, %{body: "Some html with weather data"}}
end)
assert {:ok, _} = Bound.get_weather("Chicago")
end
end
endFunction heads and pattern matching can represent input constraints, although this example uses an explicit assertion. Mox identifies concurrent use as a core feature. Tests sharing a mock can still use `async: true`, provided each test registers its own expectations and the surrounding setup does not introduce unrelated shared state. Mox does not document private mock state, automatic reset scheduling, call ordering, or wildcard matching, so those behaviors should not be assumed from this example.
Typespec checking and Hammox's different approach
Mox uses behaviours to define the mock surface, but the README does not claim that generated mock calls automatically enforce every typespec constraint declared by the behaviour. The project instead points to Hammox as an "enhanced version of Mox" that automatically checks calls against behaviour typespecs.
That is a meaningful difference in approach. Mox gives the test a function-based place to inspect input and choose output. Hammox adds automatic consistency checking against declared typespecs. A team that wants a mismatch to fail immediately because of a return or argument type may prefer Hammox, while a team that only needs a contract-backed substitute may find Mox sufficient.
The README does not document Mox call ordering, automatic reset behavior, wildcard expectations, or a separate CLI. Evaluation should therefore focus on the documented features rather than assumptions about other mocking APIs. The same restraint applies to maintenance. The repository is not archived and its last push was on 2026-10-16, but it has no GitHub releases, and the README does not provide a migration policy. The stated Apache License, Version 2.0, identifies the license without resolving application-specific legal questions.
Editorial conclusion
Adopt Mox when Elixir tests need behaviour-backed mocks that remain compatible with `async: true` and explicit call assertions. Do not choose it for ad-hoc substitutes, per-test dynamic modules, or automatic enforcement of callback typespecs. Verify first that the application has suitable behaviours, that configuration selects the mock only in tests, and that expected interactions can be expressed with function clauses.
Frequently asked questions
How do I install Mox in an Elixir project?
Add `{:mox, "~> 1.0", only: :test}` to the dependency list in `mix.exs`. Mox normally starts automatically, unless the project sets `:applications` in `def application`.
Can Mox create a mock without an Elixir behaviour?
No. The README states that Mox has no ad-hoc mocks and that mocks can only be created from behaviours. The dependency must therefore have an appropriate callback contract.
Does Mox support concurrent Elixir tests?
Yes. The README says tests sharing the same mock can still use `async: true`, and it recommends defining the mock before test execution rather than generating one for every test.
Does Mox automatically enforce behaviour typespecs?
The README does not say that it does. It identifies Hammox as an enhanced alternative that automatically checks mock calls against behaviour typespecs.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dashbitco-mox)Star history chart for your README
[](https://hysenlabs.com/projects/dashbitco-mox)Was this useful?