Guides
Record & replay
The bridge between the two things the harness already does: passthrough mode runs a suite against a real server, and @MongoSeed replays fixtures. Recording captures what a real run actually needed and emits it as a ready-made fixture directory. A machine handles the single biggest cost of a migration.
The one-off recording run
Recording is a property, not a mode: point bonsai.record.output at a directory and run the suite in passthrough mode, once. It is available in the Spring harness only (3.4.0+), because passthrough is.
mvn verify -Dbonsai.mode=passthrough -Dbonsai.record.output=target/bonsai-recordingsRecording is observationally inert: it snapshots, it never writes, and nothing it does can fail a suite. A class whose recording cannot be trusted is refused in the manifest, by name. The tests themselves are untouched, and a run that records nothing still passes or fails on its own merits.
What lands on disk
target/bonsai-recordings/com.acme.OrderQueryTest/
├── class-seed/… → the world every method starts from (@MongoSeed directory layout)
├── method-seed/<method>/… → only methods whose pre-body state differs from that baseline
├── manifest.json → scope, namespaces, counts, hashes, versions, completeness, errors
├── RECORDING.md → exact replacement instructions; read this first
└── .bonsai-recording → marker: gates percent-decoding of encoded names on replayThe class baseline is taken after suite and class seeds have been applied and before the first method body runs. This is the world every method in that class starts from. A method delta is written only for methods whose pre-body state differs from that baseline, computed against a fresh post-cleanup reference, so the common case emits nothing per method.
The layout is the ordinary @MongoSeed directory convention: a file per collection and a subdirectory per database, written as canonical Extended JSON and published atomically, so a half-written artifact never appears. Collection names that are not filename-safe are percent-encoded on the way out; the .bonsai-recording marker is what tells the loader to decode them again on replay.
The replacement contract
Recorded fixtures replace the class's original seed declarations. They do not stack on top of them. RECORDING.md is generated per class and says exactly which annotations to remove and what to add, including clearing bonsai.seed.locations when suite seeds were captured, because they are already inside the baseline.
Scope and completeness
The default scope is the databases the class declared or was observed touching, and such an artifact records "complete": false. That is honesty about the passthrough boundary rather than caution: writes the application makes to a database it never declared are invisible from where the recorder sits.
Give bonsai.record.databases an explicit allowlist and the manifest records "complete": true instead. Replay-safety claims attach only to complete artifacts. An incomplete recording is a useful starting point, not a proof.
Every snapshot is double-read and compared. An unstable pair, such as a background writer or a scheduled job, is retried a bounded number of times, and if it still will not settle, that class's recording is refused by name in the manifest rather than published as a fixture that never quite reproduces.
Frozen placeholders
A fixture full of ${#now} and ${#objectId} has already been resolved by the time a recorder can see the store. Rather than refuse the recording, the recorder writes the values it observed: dynamic placeholders are frozen at their recorded values. The manifest's placeholderSources names which fixtures carried which placeholders, so a human can decide which ones to re-dynamise deliberately. See Placeholders.
Safety and volume guards
- Remote servers are refused unless
bonsai.record.allow-remote=true. Recording reads a real deployment; doing that to a non-local host should be a decision, not a default. - Names cannot escape the output directory. Collection names come from the server, so they are percent-encoded and containment-checked before they become paths, with case collisions refused rather than merged.
- Volume is capped by
bonsai.record.max-documents(50,000 by default), with a named refusal rather than a directory that quietly grows to a gigabyte. - Error text in manifests is truncated, so a failure message from a real deployment cannot smuggle a wall of data into a committed file.
Indexes are captured, not replayed
The manifest records every index spec it saw and RECORDING.md lists them, but replaying a recording does not create them. If the suite depends on an index, remember that unique indexes change behaviour because they produce duplicate-key errors. Declare it through bonsai.indexes-file, as described in Configuration.
Verifying a recording
BonsaiRecordings.verify(Path) answers the question a committed fixture eventually raises: does this artifact still describe the store?
BonsaiRecordings.verify(Path.of("src/test/resources/recorded/com.acme.OrderQueryTest"));It byte-compares the live embedded store against the artifact through the same canonical writer the recorder used: UTF-8, LF line endings, _id-sorted documents, and _id-first field order. It reports the first differing namespace, _id and byte offset rather than a diff you have to read. It also checks the manifest's completeness and versions, and refuses refused or unknown artifacts by name. The evaluation module's passthrough lane runs the whole record → replay → byte-identical gate in CI.
Properties
| Property | Default | Description |
|---|---|---|
bonsai.record.outputString | — | Enables recording, and is the artifact root. One FQN-named directory per test class, published atomically under a lockfile. |
bonsai.record.includeString | — | A fully-qualified-class-name glob restricting which classes are recorded. Note that ? is a literal here, not a wildcard. Class names contain no question marks, and a glob metacharacter that matches nothing would only mislead. |
bonsai.record.databasesList<String> | — | An explicit database allowlist. Recording it makes the manifest "complete": true; without it, scope is inferred and the manifest says false. |
bonsai.record.allow-remoteboolean | false | Allows recording against a non-local host. Off by default. Recording reads a real deployment. |
bonsai.record.max-documentsint | 50000 | Volume cap across the recording. Exceeding it is a named refusal, not a truncated artifact. |