# Hammox checks that your mocks and your implementation agree

> Hammox is an Elixir library that extends Mox with runtime type checking, so a mock that no longer matches its behaviour typespec fails the test rather than passing silently, and real implementations can be wrapped with the same checks. It suits an Elixir team that wants contract tests without writing them by hand, and it is a drop-in replacement for Mox.

**msz/hammox** — 🏝 automated contract testing via type checking for Elixir functions and mocks

- Repository: https://github.com/msz/hammox
- Stars: 598 · Forks: 32
- Language: Elixir
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/msz-hammox

## The failure it exists to prevent

The README walks through the failure mode in detail, and it is the clearest argument for the library. You have a database behaviour whose get_users callback returns a list of binaries, and a Stats module that counts them. With Mox, the test creates a mock, sets an expectation and asserts, and it passes. Later you want error flagging, so you change the real implementation and the behaviour to return an ok/error tuple. The Stats test still passes, even though the function will break when the real database client is used. The reason is that the mock is now invalid, it no longer implements the given behaviour and therefore breaks the contract, and even though Mox is supposed to create mocks following explicit contracts, it does not take typespecs into account. That is the gap Hammox fills. Replace Mox with Hammox and the same test now fails with a type mismatch error naming the expected return type.

## Swapping Mox for Hammox is a rename

The installation and migration sections make the adoption cost small, which is what makes the library worth trying. If you are currently using Mox, you delete it from your list of dependencies in mix.exs and add :hammox with a version constraint and a test only environment:

```elixir
def deps do
  [
    {:hammox, "~> 1.0", only: :test}
  ]
end
```

Migrating from Mox means replacing all occurrences of Mox with Hammox. Nothing more is required, and all your mock calls in test are now ensured to conform to the behaviour typespec. If you are starting from scratch, the README points you at the Mocks and explicit contracts post by Jose Valim, then the Mox documentation, and suggests switching to Hammox once you are comfortable with Mox. That ordering is deliberate. Hammox is a strengthening of Mox rather than a replacement for the idea, and the library's own framing is that it takes the excellent Mox library and pushes its philosophy to its limits, providing automatic contract tests based on behaviour typespecs while maintaining full compatibility with code already using Mox.

## Protecting the real implementation closes the triangle

Checking mocks against behaviours is only two corners of the problem, and the README asks the question directly: what about the real implementations, since the real goal is to keep all units implementing a given behaviour in sync. The answer is Hammox.protect/2, which takes a function reference and a behaviour and returns an anonymous function you can use in place of the original module function. The test then asserts on the protected function's result:

```elixir
defmodule RealDatabaseTest do
  use ExUnit.Case, async: true

  test "get_users/0 returns list of users" do
    get_users_0 = Hammox.protect({RealDatabase, :get_users, 0}, Database)
    assert {:ok, ["real-jim", "real-joe"]} == get_users_0.()
  end
end
```

The library also provides a setup_all friendly version, which takes the implementation module and the behaviour module and returns a map of all callbacks defined by the behaviour as decorated implementation functions, so the test context carries a function per callback. There is also a macro form, use Hammox.Protect with a module and a behaviour option, which locally defines protected versions of the functions under test as if you had imported the module. The three forms are a trade of explicitness for convenience, and the README frames the macro version as trading explicitness for some macro magic. Setting the protected functions up in a setup_all hook and pulling them out of the context is the pattern the library itself suggests.

## Why not just Dialyzer

There is a section asking why use Hammox when you have Dialyzer, and the answer is short and technical. Dialyzer cannot detect Mox style mocks not conforming to typespec. The main aim of Hammox is to enforce consistency between behaviours, mocks and implementations, and that is best achieved when both mocks and implementations are subjected to the exact same checks. The distinction the README draws is that Dialyzer is a static analysis tool while Hammox is a dynamic contract test provider, that they operate differently, and that one can catch some bugs when the other does not. That framing matters for how you deploy them. Dialyzer reasons about your code without running it, so it will never see the mock you wrote in a test, and it will never notice that the mock and the behaviour have drifted apart. Hammox runs the mock and the implementation through the same check at test time, which is exactly the point at which the drift becomes visible.

## The error message is designed to be actionable

The project states that Hammox aims to catch as many contract bugs as possible while providing useful deep stacktraces so they can be easily tracked down and fixed, and the example error shows what that means in practice. Instead of a bare assertion failure, the test raises a Hammox.TypeMatchError naming the function, the value that was returned and the type that was expected:

```none
** (Hammox.TypeMatchError)
Returned value ["joe", "jim"] does not match type {:ok, [binary()]} | {:error, term()}.
```

The message tells you three things at once, which is more than a plain type error usually gives you: what the mock actually produced, what the behaviour now requires, and that the mismatch is between the two. In the worked example that is enough to diagnose the problem without opening the behaviour module, because the expected type in the message shows the ok/error tuple that was just introduced. The deep stacktrace part is a separate concern from the message, and it matters for the case where the failing function is called several levels down from the assertion, which is common in a contract test over a real implementation.

## Package shape and release history

The repository is a normal Elixir package with a .formatter.exs, a mix.exs and mix.lock, lib/ for the source, test/ for the tests, guides/ for the documentation guides, a CHANGELOG.md and a LICENSE, and the CI badge points at a GitHub Actions workflow named ci.yml. The library is published on Hex as hammox, with the module documentation on hexdocs.pm, and the licence is Apache-2.0. The release history shows v0.7.1 on 2025-07-26, then v0.7.0 in July 2022 and v0.6.1 in June 2022, so the project had a long gap and then a recent release. The dependency line in the install instructions asks for the 1.0 series, which suggests the published versioning moved past the 0.x line that the older tags represent. The last push was on 2026-09-14, so the code is being touched even where the tag cadence is quiet, and the package carries a mix of static type checking libraries of its own rather than depending on a large framework.

## Conclusion

Adopt Hammox when you already use Mox and have been caught by a mock that stopped matching its behaviour after a typespec change, since that is the exact failure it exists to prevent. Do not adopt it if you are not using explicit behaviours, since the checking is anchored on the typespec of a behaviour module, and do not expect it to replace Dialyzer, since the README is clear that one is static analysis and the other is a dynamic contract test provider. Verify first that your Mox version is compatible with the Hammox release you pin, that the behaviours you rely on have callback typespecs written, and that you understand how the protected function wrappers interact with your own setup hooks before you adopt them across a large suite.

## FAQ

### What problem does Hammox solve that Mox does not?

Mox does not take typespecs into account, so a mock can stop matching its behaviour after the behaviour changes and the test still passes. Hammox applies the behaviour typespec to the mock at runtime, so a mock returning a value that no longer matches the callback raises a Hammox.TypeMatchError instead of passing quietly.

### How do I migrate from Mox to Hammox?

Delete Mox from your dependencies in mix.exs, add {:hammox, "~> 1.0", only: :test}, and replace all occurrences of Mox with Hammox. Nothing more is required, and all your mock calls in test are then ensured to conform to the behaviour typespec.

### How do I check a real implementation against its behaviour?

Use Hammox.protect/2 with a function reference and the behaviour module, which returns an anonymous function you call in place of the original. There is also a setup_all friendly version taking the implementation module and behaviour module that returns a map of all callbacks as decorated functions, and a use Hammox.Protect macro form that defines protected versions locally.

### Does Hammox replace Dialyzer?

No. The README states that Dialyzer is a static analysis tool while Hammox is a dynamic contract test provider, that they operate differently, and that one can catch some bugs when the other does not. Dialyzer cannot detect Mox style mocks that do not conform to a typespec, which is the gap Hammox fills.

## Sources

- [Issues](https://github.com/msz/hammox/issues)
- [License: Apache-2.0](https://github.com/msz/hammox/blob/master/LICENSE)
- [msz/hammox on GitHub](https://github.com/msz/hammox)
- [README](https://github.com/msz/hammox/blob/master/README.md)
- [Releases](https://github.com/msz/hammox/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/msz-hammox
