Skip to content

Build1 publisher2 min readPublished

ArchUnitTS fails an architecture rule that inspected zero files

A dev.to walkthrough turns seven layering rules into ordinary test assertions in a NestJS project. The default that decides whether those rules mean anything is what the library does when a folder pattern matches no files.

The Engineer · Build desk

Illustration accompanying ArchUnitTS fails an architecture rule that inspected zero files

What happened

  • A dev.to walkthrough opens on a familiar failure: the docs got written, the reviews happened, somebody explained the onboarding, and six months later controllers are importing a repository directly with nobody noticing.
  • The tool it uses is ArchUnitTS, described as an actively maintained architecture-testing library for TypeScript inspired by ArchUnit from the Java ecosystem, installed as a dev dependency.
  • Seven rules are shown against a NestJS src tree: three constrain dependency direction between domain, application, infrastructure and http, and four check file naming, interface prefixes and circular dependencies.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Failing on zero matches turns a directory rename into a build event: move src/domain and every rule naming the old path goes red until the strings are updated.
  • decision Empty-match behaviour becomes the criterion for picking an architecture-testing library, because a library that passes a rule which inspected nothing produces the same green build as having written no rules.
  • cost The enforcement bill moves from people to machine time, on the post's own accounting that pair programming cannot cover every change and that review catches violations after the code exists.

`projectFiles().inFolder('src/domain').shouldNot().dependOnFiles().inFolder('src/application').or().inFolder('src/infrastructure')` returns a rule object, and the check is `await expect(rule).toPassAsync()` [8], an ordinary async expectation. It runs in the test runner you already have, inside the CI job you already pay for, and the only new artefact is a dev dependency [6]. The post is explicit that these tests inspect the structure of the application and not the business logic [4].

The pattern itself is a string, and nothing validates it against the directory tree. Write `inFolder('src/doamin')` and, in the post's account, many libraries match zero files, perform zero checks, and report that the test passed [13]. A one-character difference inside a string literal slides straight past a reviewer. The dev.to post calls a passing test that inspected zero files "worse than no test at all" and "exactly the kind of false confidence that causes real incidents" [15]. ArchUnitTS fails empty tests by default, and the test will not pass until the pattern matches actual files [14]. Anyone comparing tools has to write a deliberately misspelt rule and watch whether it goes green, because the post never names the libraries that behave the other way.

For these rules to transfer, the layers have to be folders. Every example addresses `src/domain`, `src/application`, `src/infrastructure` and `src/http` [7], and the rule language selects files by folder and then asserts on dependencies between folders [8]. A repository that keeps layers in workspace packages, or that groups files by feature, needs patterns written against that tree.

Three of the checks are conventions and not dependency direction: `matchPattern('*UseCase.ts')` under `src/application/use-cases` [10], and `haveNameMatching(/^I[A-Z]/)` applied to interfaces in both `src/domain` and `src/infrastructure` [11]. Those encode team agreements. They fail the day the team changes the agreement, and the repair is editing a regex in a test file.

The cycle check is pointed at `src/**` [12]. The walkthrough does not report how long any of the checks take on a real project [18], so whether whole-tree cycle detection belongs in the pre-merge job or a nightly one is left open.

What to watch

  • Whether ArchUnitTS keeps failing zero-match rules as the default, or adds a flag that permits empty checks.
  • Runtime figures for haveNoCycles() over src/** on a large repository. Those numbers decide whether the check sits pre-merge or in a nightly job.
  • Rule syntax written against workspace packages or tsconfig paths, for repos where layers are not directories.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories