Guides
Passthrough mode
Point the same unchanged test suite at a real MongoDB server, and diff green against green. One property switches the backend; nothing in your tests moves.
The idea
The library's own differential lane proves operation-level parity against real MongoDB. A consuming team's residual risk is narrower and more personal: does that parity hold for our application's specific usage?
Passthrough mode answers that for teams that have Docker somewhere. A nightly pipeline, say, even when developer machines and pull-request runners do not. Flip one property and the same annotation, the same seeding, the same cleanup, the same await helpers and the same assertions all operate against a real server via a supplied URI. The harness features are backend-neutral by construction; only the client underneath changes.
A team can then run its unchanged integration suite both ways and compare. That turns “trust the library's own lane” into “verified on our own codebase”. That is a statement about your suite specifically, which is why this library cannot make it on your behalf.
The mode contract
bonsai.mode accepts exactly two values: embedded, the default, and passthrough. In passthrough, bonsai.passthrough.uri is required.
# CI profile "mongo-verify": same tests, real MongoDB, zero code changes
bonsai.mode=passthrough
bonsai.passthrough.uri=${MONGO_VERIFY_URI}| Property | Default | Description |
|---|---|---|
bonsai.modeString | embedded | embedded or passthrough. Any other value fails while the settings are read, naming both accepted values. |
bonsai.passthrough.uriString | — | The real MongoDB connection URI. Required when mode=passthrough; ignored, with a WARN, when mode=embedded. |
bonsai.passthrough.allow-remoteboolean | false | Required opt-in when any URI host is not localhost, 127.0.0.1, or ::1. |
Both failures are raised while the settings are read, before any client is created and before the store context is opened. A misconfigured test therefore leaves nothing behind to clean up.
java.lang.IllegalStateException: bonsai.mode=passthrough requires
bonsai.passthrough.uri to be set to a real MongoDB connection URIAn unknown mode fails the same way, naming what is accepted rather than guessing:
java.lang.IllegalStateException: bonsai.mode=in-memory is not a
test mode — use 'embedded' or 'passthrough'The mirror case is treated the same way. A passthrough URI left in place while the mode is embedded is ignored, and says so:
WARN SharedMongoContextInitializer - Ignoring bonsai.passthrough.uri
because bonsai.mode=embedded; the embedded test store remains activeThat combination usually means a half-finished profile switch, and the WARN is what turns “why is this still fast?” into one line of build log.
What the harness still does
Everything the harness offers is expressed against com.mongodb.client.MongoClient, MongoDatabase and MongoCollection, the driver's own interfaces. Nothing in the support, seeding, await or assertion code reaches for a shim type, which is why all of it works unchanged against a real server:
- Declared schema: the annotation's
databasesandcollections, the matching properties, and the schema file with its indexes, pre-created on the real server. - Seeding:
@MongoSeedat class and method level, suite-levelseed.locations,MongoTestData, every fixture format and the placeholder resolver. - Cleanup: the same
bonsai.cleanupcontract, withdrop-databasesdropping only the known databases declared or touched through the harness, then reapplying the declared schema and the seeds. Passthrough cleanup never enumerates the real server's databases. - Await helpers and assertions:
MongoTestAwait, the collection, document, snapshot and choreography assertions, and their failure rendering.
Because seeds are reapplied after each cleanup exactly as in embedded mode, a suite that depends on a clean, seeded starting state per method behaves identically. That property is pinned by an evaluation integration test whose two ordered methods prove seed reapplication after a real-server cleanup.
What changes
- No shim. No embedded store is opened and no
sharedMongoTestClientbean is registered. - No bean overrides. The bean post-processor that rewires an application's own
MongoClientandMongoDatabaseFactorybeans is not installed. There is nothing to rewire them to. The real client is the point. - No URI neutralisation. The initializer publishes the supplied URI as
spring.mongodb.uri, along withspring.mongodb.database,bonsai.active=trueandbonsai.mode=passthrough, and then leaves Spring Boot to build its own realMongoClientfrom it, exactly as it would in production. - A separate client for the harness. The initializer creates its own real driver client for the harness context; that client is owned by the context and closed with it.
- The URI source differs. Default-database resolution consults
bonsai.passthrough.urirather thanspring.mongodb.uri.
Your test code changes not at all. The class that runs against the shim is the class that runs against the server:
@SpringBootTest
@EnableMongoEmulator(collections = {"orders", "outbox"})
@MongoSeed("classpath:mongo/seed/")
class OrderFlowIT {
// unchanged. The same class runs in both modes.
}The nightly mongo-verify job
The intended shape is a dedicated CI profile that reuses the existing suite verbatim. Nothing in the tests changes; nothing in the application changes. Keep the pull-request lane on embedded. That is the lane that has to run without Docker. Give the nightly lane the real server:
# Nightly only: the pull-request lane keeps running embedded, with no Docker.
MONGO_VERIFY_URI=mongodb://mongo-verify:27017/orders \
mvn -q verify -Dspring.profiles.active=mongo-verifyThen diff the two results. Green against green is the outcome you want, and any disagreement is exactly the signal this mode exists to produce: either a real divergence worth reporting, or something about your application that only shows up against one of the two. Running it as a standing nightly job during adoption and after it is the recommended safety net.
Hazards worth knowing
Remote servers require an explicit destructive-cleanup opt-in
Passthrough refuses to start when any URI host is non-loopback. Per-method drop-databases cleanup really drops the known databases on that server, so set bonsai.passthrough.allow-remote=true only for a dedicated, disposable test deployment. With cleanup=none, neither per-method cleanup nor context shutdown drops those databases; the suite owns all state management.
This catches the common CI shape: a throwaway MongoDB service container reached at a non-loopback alias (mongodb://mongo:27017). That server is disposable, so opting in is correct there. Set allow-remote=true for that suite. The guard exists precisely so a URI that accidentally points at a shared or production server cannot silently drop its databases between test methods.
- Cleanup is scoped, but still server-visible. Passthrough cleanup drops only databases the test declared or seeded/touched through harness APIs: annotation and property declarations, index-file databases, the resolved default,
@MongoSeedtargets, andMongoTestData/MongoTestSupportrouting. It never enumerates the server, so a database created directly outside the harness is out of scope and survives. Application writes to another database are also out of scope; declare that database indatabases()or adatabase.*entry ofbonsai.collectionswhen it must be cleaned. A dedicated test server or test deployment is still recommended. - Transactions need a replica set. A single standalone
mongodwill reject them. This is real-MongoDB behaviour rather than anything the harness does, but it is the first thing a passthrough run tends to hit. - Parallel classes now share one server. Embedded mode isolates each context in its own namespace; in passthrough, isolation belongs to the server you supplied. Two suites pointed at the same URI at the same time will clean up under each other.
- The shim's fail-fast boundaries disappear. Operations that raise
MongoTestUnsupportedExceptionin embedded mode simply work against a real server. That is the point of verifying, and it is also a reason a passthrough-only green is not evidence that the embedded lane is green.
See Modes for how passthrough sits beside embedded and the durability debug store, and Supported operations for what the emulation covers and the boundaries it fails fast on.