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
three small annotations—step, show, 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 |
show | bounded preview of data flowing through the scenario |
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.2.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
|> show("Created sale")
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.
Showing data as it flows
show/2 records a bounded inspect preview and returns the original value, so
it fits naturally into pipelines:
order
|> calculate_totals()
|> show("Order after totals")
|> apply_discount()
|> show("Order after discount")
|> persist()
The report displays each preview between the surrounding steps and checks. It does not serialize the original term: collections, strings, and the final preview are limited to keep reports readable.
Do not show credentials, tokens, personal data, or other secrets. Report files are often retained as CI artifacts.
Choosing useful annotations
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.
showreturns the exact input value and records only a bounded string preview.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
step,show, andcheckitems are rejected. - The output directory is fixed to
ex_unit_atlas_report. - Skipped and excluded tests are omitted from schema version 2.
- Logger output is not captured; use
showfor intentional data previews. - 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.