Embedded MongoDB (Testing)
The mongo4cats-embedded module starts a temporary in-process MongoDB instance for use in tests. It is built on top of de.flapdoodle.embed.mongo and downloads the appropriate MongoDB binary automatically on the first run (cached in ~/.embedmongo afterwards).
Setup
Add the dependency scope-limited to tests:
libraryDependencies += "io.github.kirill5k" %% "mongo4cats-embedded" % "<version>" % Test
Basic usage with ScalaTest
Extend the EmbeddedMongo trait and wrap your test body in withRunningEmbeddedMongo:
import cats.effect.IO
import cats.effect.unsafe.implicits.global
import mongo4cats.bson.Document
import mongo4cats.bson.syntax._
import mongo4cats.client.MongoClient
import mongo4cats.embedded.EmbeddedMongo
import org.scalatest.matchers.must.Matchers
import org.scalatest.wordspec.AsyncWordSpec
class MyRepoSpec extends AsyncWordSpec with Matchers with EmbeddedMongo {
// Default port is 27017. Override if you need a different one:
override val mongoPort: Int = 12345
"MyRepository" should {
"save and find a document" in withRunningEmbeddedMongo {
MongoClient.fromConnectionString[IO]("mongodb://localhost:12345").use { client =>
for {
db <- client.getDatabase("testdb")
coll <- db.getCollection("items")
doc = Document("name" := "widget", "qty" := 10)
_ <- coll.insertOne(doc)
found <- coll.find.first
} yield found mustBe Some(doc)
}
}.unsafeToFuture()
}
}
withRunningEmbeddedMongo starts the instance before the block runs and stops it after, regardless of whether the block succeeds or fails.
Choosing a MongoDB version
Both the Cats Effect and ZIO embedded helpers default to MongoDB 8.0.23. Override mongoVersion in your suite to run against another supported version:
import de.flapdoodle.embed.mongo.distribution.Version
override protected val mongoVersion: Version = Version.V8_0_23
Client-level bulk writes require MongoDB 8.0 or later. Changing the embedded default does not change the server requirement of existing collection operations. Flapdoodle determines which server binaries are available for your operating system; mongo4cats chooses the default version above.
Specifying a port explicitly
If you need to run multiple embedded instances or a specific port:
"use a custom port" in withRunningEmbeddedMongo(27099) {
MongoClient.fromConnectionString[IO]("mongodb://localhost:27099")
.use(_.listDatabaseNames)
.map(_ must contain("admin"))
}.unsafeToFuture()
The helper accepts a port, not a host/port pair. Connect to the embedded instance on localhost using the same port.
Using with Cats Effect IOApp / munit-cats-effect
import cats.effect.IO
import mongo4cats.bson.Document
import mongo4cats.bson.syntax._
import mongo4cats.client.MongoClient
import mongo4cats.embedded.EmbeddedMongo
import munit.CatsEffectSuite
class MyMunitSpec extends CatsEffectSuite with EmbeddedMongo {
override val mongoPort: Int = 27099
test("inserts and retrieves a document") {
withRunningEmbeddedMongo {
MongoClient.fromConnectionString[IO]("mongodb://localhost:27099").use { client =>
for {
db <- client.getDatabase("testdb")
coll <- db.getCollection("docs")
doc = Document("hello" := "world")
_ <- coll.insertOne(doc)
found <- coll.find.first
} yield assertEquals(found, Some(doc))
}
}
}
}
ZIO variant
For ZIO-based test suites use the mongo4cats-zio-embedded module instead. See the ZIO section for details.
Each withRunningEmbeddedMongo call uses its own scope. The resources acquired inside the block are released before MongoDB stops, including when the block fails or is interrupted. Cleanup finishes before the helper returns, so successive calls can reuse the same port even inside a longer-lived outer scope. The lower-level EmbeddedMongo.start instead keeps the process alive until its caller's scope closes.
When using ZIOSpecDefault, run embedded MongoDB tests with a live clock so startup retries can advance, and set a timeout to bound failed tests:
import zio.durationInt
import zio.test.TestAspect
// Apply these aspects to your integration test suite:
// suite(...) @@ TestAspect.sequential @@ TestAspect.withLiveClock @@ TestAspect.timeout(2.minutes)
Notes
- Each call to
withRunningEmbeddedMongostarts a fresh instance. Data does not persist between calls. - The embedded instance is a real MongoDB process — queries behave identically to a real deployment.
- The binary is downloaded from the internet on first use. Subsequent runs use the cached binary.
- The default MongoDB version is pinned by mongo4cats. Override
mongoVersionto select a different version supported by the bundled Flapdoodle dependency.