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(Indexes.ascending("organisationId"), Indexes.descending("createdAt")) // 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 (none by default), such as a migration tool's bookkeeping collections. 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")
}