ExUnit Atlas
ExUnit Atlas turns ordinary ExUnit tests into a readable map of the behavior your application guarantees.
It is not a new test framework. ExUnit remains responsible for tests,
assertions, failures, stacktraces, terminal output, and exit codes. Atlas adds
two small annotations—step and check—and generates:
ex_unit_atlas_report/
├── report.json
└── index.html
The HTML report is a self-contained document with no server, JavaScript, external assets, or frontend build step.
Why Atlas?
Test names are useful, but a test often guarantees more than its name can say. Atlas lets the test body expose those guarantees without replacing familiar ExUnit structure:
| ExUnit | Atlas interpretation |
|---|---|
| module or file | behavior group |
describe | behavior section |
test | scenario |
step | meaningful setup or action |
check | named guarantee |
| ExUnit result | scenario status |
The resulting report is meant to be read from top to bottom as business behavior, while technical metadata remains available when needed.
Installation
Add Atlas as a test-only dependency:
def deps do
[
{:ex_unit_atlas, "~> 0.1.0", only: :test, runtime: false}
]
end
Run mix deps.get.
In test/test_helper.exs, replace the existing ExUnit.start() call with:
ExUnit.start(
formatters: [
ExUnit.CLIFormatter,
ExUnitAtlas.Formatter
]
)
Keep the standard CLI formatter. Atlas complements it; it does not print the normal test results.
Usage
Add use ExUnitAtlas after use ExUnit.Case or your project-specific case
template:
defmodule Shop.SaleTest do
use ExUnit.Case, async: true
use ExUnitAtlas
describe "Sales" do
test "cash sales are fiscalized" do
sale =
step "Create a completed cash sale" do
%{payment_type: :cash, status: :completed}
end
check "The completed cash sale is eligible for fiscalization" do
assert sale.payment_type == :cash
assert sale.status == :completed
end
end
end
end
Run tests normally:
mix test
Open ex_unit_atlas_report/index.html in a browser, or consume
ex_unit_atlas_report/report.json from another tool.
Choosing useful steps and checks
Use a step for preparation or an action that carries business meaning. Use a
check to name a guarantee and keep regular assert and refute expressions
inside it.
Do not add a step only because every scenario appears to need one. This is perfectly valid:
test "disabled fiscalization overrides the request" do
check "Fiscalization remains disabled" do
refute should_fiscalize?(request, %{fiscalization_type: :disabled})
end
end
The report becomes valuable when names explain behavior—not when every line of test code receives a label.
Runtime semantics
- Instrumented blocks execute exactly once.
- Their return values are returned unchanged.
error,exit, andthrowretain their original reason and stacktrace.- Assertion messages and useful user frames remain visible.
- Concurrent tests keep independent, ordered item streams.
- Passing and failing suites both produce reports.
- ExUnit keeps control of terminal output and the process exit status.
- Without
ExUnitAtlas.Formatter, blocks still execute normally but no report is generated.
Current limitations
- Nested
stepandcheckblocks are rejected. - The output directory is fixed to
ex_unit_atlas_report. - Skipped and excluded tests are omitted from schema version 1.
- Captured output and logs are not included.
- Atlas does not automatically register its formatter.
Documentation
Requirements
- Elixir 1.14 or later
- OTP 25 or later is the currently verified baseline
- No runtime dependencies
Contributing
Bug reports and focused improvements are welcome. Read CONTRIBUTING.md before opening a pull request. By participating, you agree to follow the Code of Conduct.
For release validation and publication, see RELEASING.md.
License
ExUnit Atlas is available under the MIT License.