You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
  });
});

关键修复点说明

  1. 避免重复连接:mongoConnect()新增了连接状态检查,确保同一环境下仅创建一次连接
  2. 测试环境隔离:通过cleanupTestDb函数统一关闭客户端、停止内存数据库,避免测试间的状态污染
  3. 服务器启动控制:app.ts不再自动启动服务器,测试时直接使用chai.request(app)即可,无需监听端口

内容的提问来源于stack exchange,提问作者paulin-crtn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 23:24:47