yoavgeva

yoavgeva

ArchTest – Architecture rules as tests. Enforced from bytecode.

ArchTest is an ArchUnit-inspired architecture testing library for Elixir. Rules live in plain ExUnit tests, run with mix test, and produce structured failure output listing every violation — with zero changes to your production code.

The problem it solves

Elixir has great tools, but there’s a gap:

  • Credo catches style issues and code smells within a file — it won’t tell you your domain layer is calling your web layer

  • Boundary gives compile-time warnings for declared boundaries, but requires annotating every module and can’t easily express transitive rules or glob-based selections

  • ArchTest fills the rest: dependency direction, transitive paths, cycle detection, naming conventions, coupling metrics — all in ExUnit, no production code touched

What it does

Write architecture rules as regular tests:

elixir

defmodule MyApp.ArchTest do
  use ExUnit.Case
  use ArchTest

  test "services don't call repos directly" do
    modules_matching("MyApp.**.*Service")
    |> should_not_depend_on(modules_matching("MyApp.**.*Repo"))
  end

  test "no circular dependencies" do
    modules_matching("MyApp.**") |> should_be_free_of_cycles()
  end

  test "no Manager modules exist" do
    modules_matching("MyApp.**.*Manager") |> should_not_exist()
  end
end

Key features

  • Dependency assertions: should_not_depend_on, should_only_depend_on, should_not_be_called_by, should_only_be_called_by, should_not_transitively_depend_on, should_be_free_of_cycles

  • Layered, onion/hexagonal, and modulith/bounded-context architecture enforcement out of the box

  • Flexible module selection with glob patterns, excluding, union, intersection, and modules_satisfying/1 for custom predicates

  • Code conventions: ban IO.puts, dbg, bare raise, undocumented public functions, and more

  • Coupling metrics: instability, abstractness, distance from the main sequence (Martin metrics)

  • Violation freeze for gradual adoption — baseline existing violations and only fail on new ones

Motivation

I built this for my own projects after wanting ArchUnit-style rules in Elixir and finding no equivalent. It works from compiled bytecode, so there’s nothing to annotate and no build step to change.

Links

https://github.com/yoavgeva/arch_test

Would love any feedback, especially on the DSL ergonomics and any rule types you’d find useful!

Showing Posts 1 to 10

felix-starman

felix-starman

I’m excited to put this in a couple apps I work on that are older and perpetually are in the middle of removing an old pattern slowly

felix-starman

felix-starman

Some igniter tasks for common patterns you think would be typical would be cool

I imagine people likely wouldn’t use it to keep things up to date, but for a first generation of stuff it would probably be useful.

dimitarvp

dimitarvp

I cannot quickly guess from the front page: are those test blocks kind of like an extension of a standard ExUnit.Case? Or are they an actual test file and assertions should go below the f.ex. modules_matching("MyApp.**.*Service")? Seems to be the latter but I want to make sure.

yoavgeva

yoavgeva OP

Thanks! That’s exactly the use case the freeze mechanism was designed for — snapshot current violations, only fail on newly introduced ones as you refactor. Let existing tech debt be,
enforce the new direction going forward.

The Freezing guide walks through the full workflow if useful.

yoavgeva

yoavgeva OP

Yes, plain ExUnit test files — use ArchTest just imports the DSL macros. Each test block is a standard ExUnit.Case test, and the assertions raise if violated:

defmodule MyApp.ArchitectureTest do
use ExUnit.Case
use ArchTest

test "services don't call repos directly" do
  modules_matching("MyApp.**.*Service")
  |> should_not_depend_on(modules_matching("MyApp.**.*Repo"))
end

end

Nothing special — just ExUnit. The pipe is the assertion itself. The Getting Started guide has the full setup walkthrough.

yoavgeva

yoavgeva OP

Great suggestion @felix-starman — shipped in v0.2.0 :tada:

mix igniter.install arch_test # basic cycle-check file
mix arch_test.gen.phoenix # Phoenix layers + naming + conventions
mix arch_test.gen.layers # web → context → repo
mix arch_test.gen.onion # domain → application → adapters → web
mix arch_test.gen.modulith # bounded-context isolation
mix arch_test.gen.naming # no Managers, schema placement
mix arch_test.gen.conventions # no IO.puts, dbg, bare raise
mix arch_test.gen.freeze # baseline existing violations

Generated files are plain ExUnit tests — edit to fit your namespaces and delete what doesn’t apply.

More information in arch_test/README.md at main · yoavgeva/arch_test · GitHub

Add {:igniter, “~> 0.7”, only: [:dev, :test], runtime: false} and you’re set.

Lucassifoni

Lucassifoni

Very nice ! I just spent a bit of the last week introducing this same idea to some of my applications. Boundary is great too but does not work on this exact problem.

In my case I chose to write a basic elixir script that I added to my CI and work with text and allowlists, with a warning and error level. Text can seem naïve (and it is) but this allowed me to surface similarities in naming in various parts of the codebase that made me think “huh, I wouldn’t want to embark a developer with those 3 things having such similar names” and made that an error.

It helps me in refactors by making the “before” state a CI and lint error, and fixing occurences one by one. I think I should migrate to your tool.

byu

byu

Very nice, I definitely want to add this to my projects.

Trying out 0.2

I first attempted mix igniter.install arch_test , and it’s failing; a manual install {:igniter, “~> 0.7”, only: [:dev, :test], runtime: false} in deps followed by compiling the dependencies fail with the same errors involving use Igniter.Mix.Task:

eg:

    error: module Igniter.Mix.Task is not loaded and could not be found. This may be happening because the module you are trying to load directly or indirectly depends on the current module
    │
  2 │   use Igniter.Mix.Task
    │   ^^^^^^^^^^^^^^^^^^^^
    │
    └─ lib/mix/tasks/arch_test.gen.conventions.ex:2: Mix.Tasks.ArchTest.Gen.Conventions (module)

It looks like arch_test has a reference to igniter, but doesn’t include igniter in its own mix deps?

yoavgeva

yoavgeva OP

Hi, Sorry about this issue, fixed on version 0.2.1, let me know if you have more issues

dimitarvp

dimitarvp

Is this not enforced by the Elixir compiler itself? I clearly remember introducing cycles and being slapped by the compiler about it.

Where Next? Top

Trending in Announcing Top

wojtekmach
Hey everyone! Req is an HTTP client for Elixir that I’ve been working on for quite some time. There is already a lot of HTTP clients out...
New
handnot2
Samly can be used to enable SAML 2.0 Single Sign On in a Plug/Phoenix application. This library uses Erlang esaml to provide plug enabl...
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
restlessronin
The repo is at GitHub - cyberchitta/openai_ex: Community maintained Elixir library for OpenAI API · GitHub. Docs are at OpenaiEx User Gu...
152 11030 135
New
shahryarjb
The Chelekom project is a library of Phoenix and LiveView components generated via Mix tasks to fit developer needs seamlessly. One of i...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
woylie
Phoenix components for pagination, sortable tables and filter forms with Flop and (optionally) Ecto. pagination cursor pagination sorta...
New

Other Trending Topics Top

mudasobwa
I am seeing a lot of aplications of Argumentum ad Vericundiam in software discussions. They do link some piece of writing and point us to...
New
bartblast
Hey folks, I just published a post about Hologram’s funding and where the project goes next - the short version: Curiosum as Main Spons...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New
Herve37
We’re evaluating API mocking tools for OpenAPI-based projects and would love to hear what other teams are using. We’re particularly inte...
New
sorenone
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New
lawik
I was thinking since Goatmire Elixir turned out pretty good I should maybe do another one. 30th of Sep - 2nd of Oct this year./ The firs...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews