Fixture factories
Suites accumulate dozens of fixture files that share one schema: Kafka payloads for one Avro record, seed datasets for one set of tables, mock bodies for one API. Most of each file is boilerplate, and the day the schema gains a required field, every file needs the same edit. A factory keeps the shared shape once and each fixture as only its differences. axx fixtures expands them into ordinary files.
The files
Section titled “The files”Everything lives next to the fixtures it produces:
kafka/ depot-scans.factory.yaml # family, schema, identities depot-scans.prototype.yaml # the shared shape scan-delivered.fixture.yaml # one fixture: only its differences scan-out-for-delivery.fixture.yaml scan-delivered.json # generated scan-out-for-delivery.json # generated# yaml-language-server: $schema=https://axx.nimbusxr.us/schemas/v0/axx-factory.schema.jsonfactory: family: avro schema: ../schemas/depot-scan.avsc # relative to this fileidentity: - path: scanId # unique across every fixturedata: parcelRef: PX-EXAMPLE-1 location: Leipzig scannedAt: "2026-05-06T10:15:00Z"data: scanId: SC-FIXTURE-DELIVERED status: DELIVEREDThe fixture’s file name is its name, and scan-delivered.json is what features reference, exactly as if you had written it by hand:
{ "scanId": "SC-FIXTURE-DELIVERED", "parcelRef": "PX-EXAMPLE-1", "status": "DELIVERED", "location": "Leipzig", "scannedAt": "2026-05-06T10:15:00Z"}Generate and check
Section titled “Generate and check”axx fixtures generate # write the fixture files, the manifest and lint rulesaxx fixtures check # CI: regenerate in memory and compare, without writing- Generation is a development-time step. Nothing is generated during
axx run; features cannot tell a generated fixture from a hand-written one. - Output is deterministic. The same spec always produces the same bytes, and every output is validated against the schema (the Avro schema here, so a
statusthat is not one of its symbols fails) before it is written. - The tool only touches files it owns.
axx-fixtures.manifest.yamlrecords them with their checksums. A managed file that was edited by hand is refused, never overwritten: move the change into the spec. - Identities become lint rules. Each
identity:entry produces a rule inaxx-lint.generated.yaml, whichlint.includepulls intoaxx lint. A fixture that omits an identity value gets one derived from its name, so it is unique by construction.
When the schema gains a required field, generation fails and names every fixture that lacks it. Add it once to the prototype (or a defaults: entry) and regenerate.
Families
Section titled “Families”| Family | Produces | Schema |
|---|---|---|
avro |
Kafka payload JSON | an .avsc file |
json |
any JSON: mock bodies, request payloads | JSON Schema, or an OpenAPI component (api.yaml#/components/schemas/Name) |
yaml |
record-shaped YAML | the same as json |
xml |
XML documents | an XSD |
protobuf |
canonical proto-JSON | a descriptor set (orders.desc#pkg.Message) |
dataset |
SQL seeds: YAML datasets, flat XML or CSV directories | optional SQL DDL (a file or a directory of migrations) |
For dataset, the prototype is a row template per table, and identities are table.column paths enforced per row. options: { format: xml } or { format: csv } in the factory writes flat XML or a CSV directory instead of YAML. The parcels example generates its rejected manifest lines as flat XML:
factory: family: dataset options: { format: xml } schema: ../../../../infra/postgres/init/01-schema.sqlProject settings
Section titled “Project settings”fixtures: sources: ["../infra/wiremock"] # extra roots, e.g. mock bodies served by a container output: { ignored: true } # generated files are gitignored, not committed conformance: # lint hand-written files against a schema too - name: seeds match the database schema filePatterns: ["seeds/*.yaml"] schemaType: dataset schemaRef: ../infra/postgres/init/01-schema.sqlWith output: { ignored: true }, generated files are derived on demand instead of committed: Axx maintains an exact .gitignore in each output directory, and a fresh clone runs axx fixtures generate once before axx run. The specs, the manifest and the generated lint rules stay committed; they are what reviewers read.
conformance rules check files that no factory owns, so hand-written seeds are held to the same schema.
Adopt existing files
Section titled “Adopt existing files”You do not need to write specs for fixtures you already have. Adoption reads existing files, writes the most common value of each field into the prototype, keeps each file’s differences in its own *.fixture.yaml, and proposes identity candidates for you to review. It only writes anything if regenerating from the new spec reproduces the originals exactly.