Express/Mocha/MongoDB内存服务器单元测试配置问题求助
问题分析与解决方案
你的核心问题是重复调用mongoConnect()导致两次数据库连接:app.ts在模块加载时自动执行了mongoConnect(),而测试代码的before钩子又再次调用了该函数,因此出现两次"Connected to MongoDB Test"日志。同时,若不在测试中调用mongoConnect(),则会因为app.ts的连接未完成(或未在测试环境正确初始化)而抛出客户端未定义的错误。
以下是针对性的修复方案:
1. 修改app.ts:避免测试环境自动启动服务器与数据库连接
将服务器启动逻辑封装为函数,仅在非测试环境或手动调用时执行,避免模块加载时自动触发数据库连接:
// app.ts export const app = express(); const port = process.env.PORT; app.use("/my-route", myRoutes); // 封装启动函数,供生产环境或手动调用 export const startServer = async () => { await mongoConnect(); app.listen(port, () => { console.log(`Listening on port ${port}`); }); }; // 仅当直接运行app.ts时(生产环境)才启动服务器 if (require.main === module) { startServer(); }
2. 优化database.ts:确保连接的幂等性与测试环境清理
添加连接状态检查,避免重复创建连接;同时保存测试环境的MongoMemoryServer实例,方便后续清理:
// database.ts import { MongoClient, Db } from "mongodb"; import { MongoMemoryServer } from "mongodb-memory-server"; let _db: Db | null = null; let _mongoClient: MongoClient | null = null; let _mongoServer: MongoMemoryServer | null = null; // 新增:保存测试服务器实例 export const mongoConnect = async () => { // 若已存在连接,直接返回 if (_db && _mongoClient) return; try { let MONGODB_URI = process.env.MONGODB_URI; if (process.env.NODE_ENV === "test") { _mongoServer = await MongoMemoryServer.create(); MONGODB_URI = _mongoServer.getUri(); } const client: MongoClient = await MongoClient.connect(MONGODB_URI); _db = client.db("dbName"); _mongoClient = client; console.log( process.env.NODE_ENV === "test" ? "Connected to MongoDB Test" : "Connected to MongoDB" ); } catch (err) { console.log("Error connecting to MongoDB:", err); throw err; } }; export const getMongoClient = () => { if (_mongoClient) return _mongoClient; throw "Mongo client doesn't exist"; }; export const getDb = () => { if (_db) return _db; throw "No database found!"; }; // 新增:测试环境专用清理函数 export const cleanupTestDb = async () => { if (process.env.NODE_ENV !== "test") return; if (_db) await _db.dropDatabase(); if (_mongoClient) await _mongoClient.close(); if (_mongoServer) await _mongoServer.stop(); // 重置全局变量 _db = null; _mongoClient = null; _mongoServer = null; };
3. 更新test.ts:统一控制测试生命周期
在测试中手动初始化数据库,使用优化后的cleanupTestDb函数完成清理:
// test.ts import { app } from "../app"; import { mongoConnect, getDb, getMongoClient, cleanupTestDb } from "../database"; import chai from "chai"; import chaiHttp from "chai-http"; chai.use(chaiHttp); const expect = chai.expect; describe("My Route Tests", () => { let db: Db; let mongoClient: MongoClient; before(async function () { // 初始化数据库连接 await mongoConnect(); db = getDb(); mongoClient = getMongoClient(); // 创建集合并插入测试数据 await db.createCollection("wordsCollection"); await db.collection("wordsCollection").insertMany(data); }); after(async function () { // 统一清理测试资源 await cleanupTestDb(); }); it("should return 200 for POST /my-route/hello", async function () { const res = await chai .request(app) .post("/my-route/hello") .send({ excludeIds: [] }); expect(res.status).to.equal(200); }); });
关键修复点说明
- 避免重复连接:
mongoConnect()新增了连接状态检查,确保同一环境下仅创建一次连接 - 测试环境隔离:通过
cleanupTestDb函数统一关闭客户端、停止内存数据库,避免测试间的状态污染 - 服务器启动控制:
app.ts不再自动启动服务器,测试时直接使用chai.request(app)即可,无需监听端口
内容的提问来源于stack exchange,提问作者paulin-crtn
相关产品推荐
相关产品推荐

