Skip to main content

MongoDB tests (invirt-mongodb-test)

Backed by Testcontainers and Kotest's TestContainerProjectExtension, so containers are started once per Kotest project and shared across specs.

testMongo​

Starts a standard MongoDB container with a replica set (required for transactions) and returns a Mongo instance pointing at a per-spec database (uuid7() suffix). The instance is closed in afterSpec.

class ProductRepositoryTest : StringSpec({
val mongo = testMongo()
val products = mongo.database.getCollection<Product>("products")

"inserts" {
products.insert(Product(name = "Pear", priceMinor = 199_00))
// ...
}
})

testMongoAtlas​

Same as testMongo, but starts a mongodb/mongodb-atlas-local container so Atlas Search and other Atlas-only features are available.

randomTestCollection​

Returns a randomly-named collection for the given document type. Useful when each test needs an isolated collection without manual cleanup.

val collection: MongoCollection<Product> = mongo.randomTestCollection()

Collection / document assertions​

collection shouldHaveAscIndex "name"
collection shouldHaveDescIndex "createdAt"
collection shouldNotHaveAscIndex "internalField"
collection shouldHaveUniqueIndex "email"
collection.shouldHaveTextIndex("title", "description")
collection.shouldHaveTimestampedIndices() // version asc, createdAt/updatedAt desc
collection.shouldHaveTtlIndex("expiresAt", expireAfterSeconds = 3600)
collection.shouldHavePartialUniqueIndex(listOf("organisationId", "email"), Document("status", "PENDING"))
collection.shouldHaveCompoundIndex("organisationId".ascKey(), "createdAt".descKey()) // exact key, 2+ fields

updated shouldBeUpdateOf previous // updatedAt later AND version greater
updated shouldBeNextUpdateOf previous // updatedAt later AND version == previous.version + 1
fetched shouldBeSameDocument original // equality ignoring version + timestamps

recordsPage.idsShouldBe(listOf("a", "b")) // any order
recordsPage.idsShouldBeInOrder("a", "b") // exact order

collection.waitForSearchDocuments(field = "title", count = 5) // Atlas Search readiness

document.createdAt.shouldBeAboutNow() // within 30s of mongoNow() by default; fails on null
document.createdAt.shouldBeAboutNow(5.seconds) // a tighter tolerance

Backdating a document​

setCreatedAt writes a document's createdAt directly, for a spec that needs a fixture to look older than insertion made it - e.g. to exercise a "created in the last N days" filter. It writes raw: unlike update, it does not bump version or touch updatedAt.

collection.setCreatedAt(id, Instant.now().minus(30, ChronoUnit.DAYS))

Clearing collections between tests​

clearCollections deletes every document from every collection except rows matching keep and any collection whose name contains one of skipCollectionNamesContaining (the mongock bookkeeping collections by default). It deletes documents rather than dropping collections, so indices a migration created survive the truncation. A null keep deletes everything.

afterEach { mongo.clearCollections(keep = "createdBy".mongoEq("data-bootstrap")) }

Spying on collections​

spyCollection produces a Mongo whose database.getCollection<Doc>(name) returns a MockK spy, so specific calls can be stubbed without affecting the rest of the application.

val mongoWithSpy = mongo.spyCollection<Product>("products") { collection ->
every { collection.insertMany(any()) } throws RuntimeException("boom")
}