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

测试清理时触发MongoTopologyClosedError的原因及解决方法

Jest中关闭MongoDB连接抛出MongoTopologyClosedError的问题排查与解决

问题场景

我在针对本地Docker部署的MongoDB执行集成测试时,在Jest的清理钩子中编写了关闭MongoDB连接的代码:

import { client } from "../lib/common/mongodb";

module.exports = async function () {
    await client.close();
};

但每次执行都会抛出以下错误,导致测试无法正常退出:

/home/ian/src/my-proj/node_modules/mongodb/lib/sdam/topology.js:217
                drainWaitQueue(this[kWaitQueue], new error_1.MongoTopologyClosedError());
                                                 ^

MongoTopologyClosedError: Topology is closed
    at /home/ian/src/my-proj/node_modules/mongodb/src/sdam/topology.ts:493:46 {
  [Symbol(errorLabels)]: Set(0) {}
}

Node.js v20.2.0
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

MongoClient单例实现

项目中使用单例模式管理MongoClient实例,代码如下:

let client;
let clientPromise;

if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
  // 开发模式下使用全局变量,保留HMR导致模块重载时的值
  let globalWithMongo = global;

  const uri = MONGO_SERVER_URL.replace("<username>", MONGO_USERNAME).replace("<password>", MONGO_PASSWORD);

  if (!globalWithMongo._mongoClientPromise) {
    client = new MongoClient(uri, options);
    globalWithMongo._mongoClientPromise = client.connect();
  }

  clientPromise = globalWithMongo._mongoClientPromise;
} else {
  // 生产模式下建议不使用全局变量
  client = new MongoClient(uri, options);
  clientPromise = client.connect();
}

// 导出模块作用域的MongoClient Promise,实现客户端跨函数共享
export default clientPromise;

export { client };

临时规避方案

目前通过捕获未处理的拒绝来强制退出进程,但这只是临时hack:

import { client } from "../lib/common/mongodb";

module.exports = async function () {
    process.on('unhandledRejection', (e) => {
        console.log("This is a hack, to deal with MongoDB not shutting down properly. It does not interfere with Jest test failures");
        process.exit(0);
    });

    await client.close();
};

原因分析

  1. 重复执行关闭操作:Jest的测试环境可能会因为模块缓存、测试套件的钩子配置等原因,导致清理逻辑被多次调用。单例MongoClient实例在第一次关闭后,再次调用close()就会触发拓扑已关闭的错误。
  2. 全局变量的测试隔离问题:在test环境下使用全局变量保存连接Promise,但Jest的测试隔离机制(如resetModules)可能导致模块被重新加载,此时导出的client可能指向已经被关闭的实例。
  3. 未检查连接状态:直接调用client.close()前没有判断连接是否处于打开状态,当连接已经关闭时就会抛出错误。

调试方法

  • 检查连接状态:在清理函数中添加日志,确认调用close()时的连接状态:
    console.log('当前连接状态是否已关闭:', client.topology.isClosed());
    await client.close();
    
  • 追踪清理函数执行次数:添加日志查看清理逻辑是否被多次触发:
    console.log('清理函数执行');
    await client.close();
    
  • 监控单例实例创建:在MongoClient单例代码中添加日志,确认实例是否被重复创建或复用:
    if (!globalWithMongo._mongoClientPromise) {
      console.log('创建新的MongoClient实例');
      client = new MongoClient(uri, options);
      globalWithMongo._mongoClientPromise = client.connect();
    } else {
      console.log('复用已有的MongoClient连接Promise');
    }
    

正确解决办法

1. 先检查连接状态再关闭

在清理函数中判断连接是否处于打开状态,仅当连接未关闭时执行关闭操作:

import { client } from "../lib/common/mongodb";

module.exports = async function () {
    if (!client.topology.isClosed()) {
        await client.close();
    }
};

2. 捕获并忽略特定错误

如果无法避免重复调用,可捕获MongoTopologyClosedError并忽略,其他错误正常抛出:

import { client } from "../lib/common/mongodb";
import { MongoTopologyClosedError } from 'mongodb';

module.exports = async function () {
    try {
        await client.close();
    } catch (err) {
        if (!(err instanceof MongoTopologyClosedError)) {
            throw err;
        }
    }
};

3. 优化单例的测试环境管理

在测试结束后清理全局变量,避免后续测试复用已关闭的连接实例:

import { client } from "../lib/common/mongodb";

module.exports = async function () {
    if (!client.topology.isClosed()) {
        await client.close();
    }
    // 清理全局变量,确保下一次测试能创建新的连接
    delete global._mongoClientPromise;
};

4. 确保清理逻辑只注册一次

如果使用setupFilesAfterEnv注册清理钩子,确保只注册一次,避免重复执行:

// setupTests.js
let cleanupRegistered = false;

if (!cleanupRegistered) {
    afterAll(async () => {
        const { client } = require("../lib/common/mongodb");
        if (!client.topology.isClosed()) {
            await client.close();
        }
    });
    cleanupRegistered = true;
}

内容的提问来源于stack exchange,提问作者Ian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 14:00:42