v3.5.0
**Nested document-array assertions.** `inArray(field)` on the document assert opens a scoped chain for state stored as nested document ar...
Full notes
Added
- Nested document-array assertions.
inArray(field)on the document assert opens a scoped chain for state stored as nested document arrays:matching(field, value)selects exactly one element (zero and many fail listing candidates by index, multi-key varargs disambiguate),at(index),single()andhasSize(n)complete the scope, the element scope is the full document vocabulary so nesting recurses, and every failure carries the full path from the collection to the failing assertion. Lambda overloads branch to several elements of one array. This replaces JsonPath filter expressions for the everyday case; a failedhasJsonPathon a filter or wildcard path now teaches the returns-an-array rule with working alternatives. - Polling assertions.
awaitAtMost(Duration)retries everything chained after it against a freshly read collection until it passes or the deadline expires (pollingEverytunes the cadence, clamped to the timeout; sleeps never overshoot the deadline). Only the collection assertion's own failures retry; the timeout failure carries the last attempt's message plus the attempt count. The handover methodsextractingField()anddocuments()refuse while polling is armed rather than silently running once, and the javadoc states that a poll cannot prove a negative. - Change tracking. Both harnesses snapshot each seeded collection once per test method, after seeding and before the test body.
documentWithId(id).changedField(path).from(v).to(w)proves the transition, not just the end state;changedField(path)alone already asserts the field existed at baseline and actually changed,unchangedField,createdFieldandremovedFieldcomplete the vocabulary, and paths take dots and[index]hops. Selection is a snapshot by_id: a document created during the test refuses for lacking a baseline, while a same-id delete-and-reinsert compares as an update. Capture is capped (bonsai.change-tracking.max-documents, default 50000, named refusal when tripped) and can be disabled withbonsai.change-tracking=off. - `present()` field matcher. Completes the presence ladder:
hasField(name)is existence (explicit null included),hasFieldMatching(name, present())is existence with a non-null value of any BSON type, typed matchers assert shape. The failure distinguishes an absent field from an explicit null. A barepresent()in the list-valued assertions is refused witheveryItem(present())guidance. - `allFieldValues(name, matcher)`. One Hamcrest matcher over every document's value for a field, in store order, with offending documents attributed by index in the failure.
- Compound and TTL index assertions.
hasCompoundIndexOn(fields...)asserts an index with exactly those ascending keys in that order (no prefix credit; the failure names prefix, reversed-order and single-field near-misses), theDocumentoverload pins directions with BSON-numeric key comparison, andhasTtlIndexOn(field, duration)matchesexpireAfterSecondsexactly. - Per-invocation seeding context.
SeedContext.testMethod()carries the test method name, resolved parameterized-test arguments and invocation ordinal for method-scoped seeds, empty at class and suite scope, so oneSeedSourceserves every@ParameterizedTestinvocation. - Registered placeholder resolvers. Any
PlaceholderResolverbean in the Spring test context (or classes on@EnableMongoEmulator(placeholderResolvers = ...)) answers fixture placeholders. The engine imposes the chain: explicit bindings, then built-ins, then registered resolvers, then the Environment fallback, so ambient configuration never shadows a declared resolver and shadowing a built-in name is a startup error. Each name resolves once per seed application and reads back viaMongoTestSupport.placeholderValue(name), cleared per method; suite-level seeds resolve through the same chain, and recordings attribute resolver-computed values by bean name inplaceholderSources. - Change-stream server-version profile (experimental).
bonsai.experimental.change-streams.server-version=7.0|8.0(default8.0) emits the chosen real server's event shape. The registry holds the one proven difference: on 7.0 an$addToSetappend reports the whole resulting array inupdatedFields, top-level and nested alike, where 8.0+ reports the appended index. Other values refuse by name (changeStream.serverVersion); each profile is verified strictly against a real server of the matching version in the differential lane.
Changed
- Change-stream event parity campaign. The differential lane now compares the change events the two backends publish, not just state: 1,029 checks green against real mongod 7.0, 8.0 and 8.3, with 28 divergences found and fixed (fullDocument field order, updateDescription granularity and emission order, removedFields ordering, deleteMany event order, commit-order delivery under concurrent writers). Two proven server behaviours are pinned honestly instead of imitated: a nondeterministic upsert seed order and the 7.x whole-array
$addToSetdelta. - Collection assertions' nested document failures now lead with the collection name in the path.
- The documentation site, skill guides and shipped examples were rewritten to remove em-dash prose, and the change-streams pages now lead with a highly-experimental warning recommending a real server where possible.
Fixed
- A
@Nestedclass declaringplaceholderResolversis refused loudly instead of silently using the enclosing class's resolvers; the change-tracking baseline and class-seed placeholder readback survivebonsai.cleanup=clearand=none; the pre-8 comparison tolerance arms on the CI service-backed differential lane; a broken javadoc reference failed the release javadoc jar.