Skip to content

Testing Mechanics

listenrightmeow edited this page Jul 17, 2026 · 1 revision

Testing Mechanics

The plumbing questions, answered directly.

Where are my specs?

Wherever feat.config.json says. The specs block defines discovery:

"specs": {
  "dir": "specs",
  "pattern": "**/*.feat",
  "contractPattern": "**/*.contract.json",
  "outputPattern": "{name}.test.ts"
}

Every .feat file under dir is a spec (directories named fixtures/ are skipped — they hold test vectors, not specs). Each spec pairs with a .contract.json of the same base name holding its JSON Schemas.

Where are the generated files?

Next to the spec that produced them, named by outputPattern:

specs/greet.feat          →  specs/greet.test.ts
specs/greet.contract.json

Generated files are complete and self-contained — resolved schemas, golden fixtures, and seed data are inlined; the only import is @mmmnt/feat-runtime. They are meant to be committed: that's what lets feat verify catch drift between specs and tests in CI. Don't edit them (the header says so, and feat verify will notice).

What is the test runner?

Vitest. Generated files are standard Vitest suites — feat run executes them through a Vitest subprocess, and because they're plain Vitest files they also run under npx vitest run specs/greet.test.ts directly, in your IDE's test integration, or anywhere else Vitest runs. Execute from the project root (paths inside spec payloads resolve from there).

How does a test actually execute?

Each generated suite, per test:

  1. Loads feat.config.json and instantiates the configured adapters (fresh instances per file).
  2. Runs given: preconditions — execute commands and seed data — before capture begins.
  3. Opens the capture window and fires the stimulus (when: command or deliver events).
  4. Waits once for any eventual-consistency services, then captures every service.
  5. Diffs captured reality against the prediction — response, records, and state — failing on anything missing or unpredicted, with failure messages that point back to the exact spec line (SPEC-EX-001 › "greets by name" › response › message).

What do coverage and reporting look like?

  • Coveragefeat run --coverage passes through to Vitest's coverage provider (@vitest/coverage-v8 or istanbul; configure thresholds in your Vitest config as usual). Generated tests exercise your implementation code, so coverage measures exactly what specs reach.
  • Test reportingfeat run writes JUnit XML when the config's report block includes "junit" (path in junitOutput). feat report summarizes the last run; feat report --junit prints the XML path for CI ingestion (any JUnit-compatible system works).

Integrating with an existing project

  1. Install the packages (Getting Started) and run npx feat init at your project root, then edit feat.config.json to match your world:
    • Point specs.dir wherever you want specs to live (a specs/ directory, or alongside code).
    • Route your commands: the handler adapter calls functions directly ({ "module": "src/handlers/create-user.ts", "export": "createUser" }); the http adapter calls a running service ({ "method": "POST", "path": "/users" } against invoke.baseUrl).
    • Declare your services — the observable surfaces you want predictions to cover — each with an adapter and a consistency model.
  2. Write one spec for one feature. Start with a single scenario and a real rejection.
  3. feat generate, commit the spec + contract + generated test together.
  4. Add the CI gate: feat verify && feat run.

Feature sits alongside your existing test suite — generated suites are contract-level tests for specified features; your unit tests remain yours. Nothing about your build changes: it's all Vitest underneath.

What the CLI writes, at a glance

Artifact Where Committed?
Specs + contracts specs.dir (you author) yes
Generated tests next to each spec (outputPattern) yes — feat verify depends on it
JUnit report report.junitOutput no (CI artifact)
Coverage output your Vitest coverage config no (CI artifact)

Clone this wiki locally