Packages

ExUnit assertion helpers for testing module dependency boundaries using Erlang's :xref.

Current section

Files

Jump to
assert_boundary lib assert_boundary.ex
Raw

lib/assert_boundary.ex

defmodule AssertBoundary do
@moduledoc """
ExUnit assertion helpers for testing module dependency boundaries.
AssertBoundary uses Erlang's `:xref` to analyze BEAM bytecode and verify
that module dependencies respect architectural boundaries. This lets you
enforce that your runtime and verification pipelines don't leak into each
other, that your domain layer stays independent, or any other structural
invariant your architecture requires.
## Usage
defmodule MyApp.BoundaryTest do
use ExUnit.Case, async: true
use AssertBoundary, app: :my_app
test "web layer doesn't touch repo internals", %{boundary: boundary} do
refute_calls(boundary, from: ~r/^MyApp\\.Web/, to: ~r/^MyApp\\.Repo\\.Internal/)
end
test "domain has restricted dependencies", %{boundary: boundary} do
assert_boundary(boundary,
modules: ~r/^MyApp\\.Domain/,
allow: [~r/^MyApp\\.Schema/]
)
end
end
`use AssertBoundary, app: :my_app` does three things:
1. Imports `AssertBoundary.Assertions` (`refute_calls/2`, `assert_calls/2`, `assert_boundary/2`).
2. Adds a `setup_all` that builds the dependency graph for the given app.
3. Makes the graph available as `%{boundary: graph}` in test context.
"""
alias AssertBoundary.Graph
@doc false
defmacro __using__(opts) do
quote do
import AssertBoundary.Assertions
import AssertBoundary.Graph, only: [under: 1]
setup_all do
%{boundary: AssertBoundary.graph(unquote(opts))}
end
end
end
@doc """
Builds a dependency graph for the given application.
Accepts a keyword list with an `:app` key, or an atom directly.
## Examples
graph = AssertBoundary.graph(app: :my_app)
graph = AssertBoundary.graph(:my_app)
"""
@spec graph(atom() | keyword()) :: Graph.t()
def graph(opts) when is_list(opts) do
app = Keyword.fetch!(opts, :app)
Graph.build(app)
end
def graph(app) when is_atom(app) do
Graph.build(app)
end
end