-
Notifications
You must be signed in to change notification settings - Fork 0
Testing Mechanics
The plumbing questions, answered directly.
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.
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).
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).
Each generated suite, per test:
- Loads
feat.config.jsonand instantiates the configured adapters (fresh instances per file). - Runs
given:preconditions —executecommands andseeddata — before capture begins. - Opens the capture window and fires the stimulus (
when:command ordeliverevents). - Waits once for any eventual-consistency services, then captures every service.
- 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).
-
Coverage —
feat run --coveragepasses through to Vitest's coverage provider (@vitest/coverage-v8or istanbul; configure thresholds in your Vitest config as usual). Generated tests exercise your implementation code, so coverage measures exactly what specs reach. -
Test reporting —
feat runwrites JUnit XML when the config'sreportblock includes"junit"(path injunitOutput).feat reportsummarizes the last run;feat report --junitprints the XML path for CI ingestion (any JUnit-compatible system works).
- Install the packages (Getting Started) and run
npx feat initat your project root, then editfeat.config.jsonto match your world:- Point
specs.dirwherever you want specs to live (aspecs/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" }againstinvoke.baseUrl). - Declare your services — the observable surfaces you want predictions to cover — each with an adapter and a consistency model.
- Point
- Write one spec for one feature. Start with a single scenario and a real rejection.
-
feat generate, commit the spec + contract + generated test together. - 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.
| 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) |