Node.js Test Tipps - MongoDB
Ich möchte den Blick erneut auf das Testen richten. Jegliche Form des Qualitätsdenkens beginnt meiner Meinung nach damit. Dieses Mal möchte ich zeigen, welche Hilfsmittel ich bei der Arbeit mit MongoDB einsetze. Ich zeige, wie ich mein Setup aufbaue, um performante Integrationstests zu ermöglichen.
In einem vorherigen Artikel (Repositories mit Prisma) hatte ich das Entwicklungsmuster der Repositories vorgestellt. Im Rahmen der Unit-Tests mocke ich auf dieser Ebene und treffe Annahmen dazu, was aus dem Repository geliefert wird und ob mein Code mit den Ergebnissen arbeiten kann.
Dieses Mal beabsichtige ich, einen Schritt zurückzutreten. Die Testausführung soll die Annahmen, die ich in den Unittests treffe, beweisen. D. h., ich will Tests schreiben, in denen ich Daten in eine MongoDB schreibe, wiederlese und sicherstelle, dass die Informationen so aussehen, wie ich es erwarte.
Wie bereits im Artikel über das Dateisystem angedeutet, spielt die Testperformanz eine große Rolle. Deswegen – und aus anderen Gründen – ist die Nutzung einer MongoDB-Instanz keine Option. Die In-Memory-Variante muss her und passend konfiguriert sein.
Das MongoDB-Test-Setup in Verbindung mit Prisma
Zuerst sind einige NPM-Pakete zu installieren:
mongodb-memory-serverfür die In-Memory-MongoDB-Variantemongoosefür die Interaktion mit der MongoDB beim Testsetup
Als Nächstes legen wir eine MongoDB-Konfiguration namens monogoConfig.ts für die Tests an:
const config = {
memory: true,
ip: '127.0.0.1',
port: '27017',
database: 'testDb',
replSet: true,
}
export default config
Im Rahmen des globalen Setups von Vitest bereiten wir die In-Memory-MongoDB vor, setzen die notwendigen Environment-Variablen für Prisma und stecken dies alles zusammen in die für Vitest vorgesehenen Methoden (siehe Vitest-Dokumentation):
import { MongoMemoryReplSet, MongoMemoryServer } from 'mongodb-memory-server'
import mongoose from 'mongoose'
import mongoConfig from './database/mongoConfig' // change according to your structure
mongoose.set('strictQuery', true)
declare global {
var __MONGOINSTANCE: MongoMemoryReplSet | MongoMemoryServer
}
export async function setup() {
// Config to decided if a mongodb-memory-server instance should be used
// it's needed in global space, because we don't want to create a new instance every test-suite
if (mongoConfig.memory) {
const instance = mongoConfig.replSet
? await MongoMemoryReplSet.create({ replSet: { count: 2, storageEngine: 'wiredTiger' } })
: await MongoMemoryServer.create()
const uri = instance.getUri()
global.__MONGOINSTANCE = instance
process.env.MONGO_URI = uri.slice(0, uri.lastIndexOf('/'))
} else {
process.env.MONGO_URI = `mongodb://${mongoConfig.ip}:${mongoConfig.port}`
}
// set prisma url for integration tests to point to in memory mongodb
process.env.PRISMA_DATABASE_URL = process.env.MONGO_URI
process.env.PRISMA_DATABASE_NAME = mongoConfig.database
// The following is to make sure the database is clean before a test starts
await mongoose.connect(`${process.env.MONGO_URI}/${mongoConfig.database}`)
await mongoose.connection.db?.dropDatabase()
}
export async function teardown() {
await mongoose.connection.dropDatabase()
await mongoose.connection.close()
// Config to decided if a mongodb-memory-server instance should be used
if (mongoConfig.memory) {
const instance = global.__MONGOINSTANCE
await instance.stop()
}
}
Ab diesem Moment kannst du in deinen Integrationstests mit der Datenbank interagieren und sicherstellen, dass die Annahmen der Unit-Tests stimmen. Selbst bei umfangreichen Tests bleibt die Ausführungszeit im Sekundenbereich. Das dadurch gewonnene Feedback zur Korrektheit zentraler Datenbankfunktionen ist meines Erachtens unbezahlbar.
Wie organisierst du deine Integrationstests? Führst du sie als Teil deines TDD-Flows aus oder Remote im Rahmen der Build-Pipeline?
Lass uns Erfahrungen austauschen