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

如何创建可Mock的MongoDB连接类?求优化现有实现

优化MongoDB+GridFS连接类,解决连接状态错误与Jest Mock问题

我帮你重构了这个连接类,既去掉了别扭的let that = this写法,又从根源上解决了连接状态错误的问题,同时让类的结构更利于Jest进行Mock测试。

先看优化后的完整代码

const { MongoClient } = require('mongodb');
const Grid = require('gridfs-stream');

class MongoDBGridFS {
  // 单例实例,确保全局只有一个连接
  static instance = null;
  // 标记连接状态,避免重复触发连接逻辑
  isConnected = false;

  constructor() {
    // 单例模式:如果已经有实例,直接返回现有实例
    if (MongoDBGridFS.instance) {
      return MongoDBGridFS.instance;
    }
    this.client = null;
    this.db = null;
    this.gfs = null;
    MongoDBGridFS.instance = this;
  }

  /**
   * 建立MongoDB连接并初始化GridFS
   * @param {string} uri MongoDB连接字符串
   * @param {string} dbName 目标数据库名称
   */
  async connect(uri, dbName) {
    // 已经连接的话直接返回,避免重复连接触发状态错误
    if (this.isConnected && this.db) {
      return;
    }

    try {
      // 用async/await替代回调,自然保留this上下文,不用额外绑定
      this.client = await MongoClient.connect(uri, {
        useNewUrlParser: true,
        useUnifiedTopology: true
      });
      this.db = this.client.db(dbName);
      // 初始化GridFS-stream
      Grid.mongo = this.client.db().nativeMongo;
      this.gfs = Grid(this.db);
      this.isConnected = true;
      console.log('MongoDB & GridFS 连接成功');
    } catch (error) {
      console.error('MongoDB 连接失败:', error);
      this.isConnected = false;
      throw error; // 抛出错误给调用方处理
    }
  }

  /**
   * 获取GridFS实例
   * @returns {GridFSStream} GridFS操作实例
   */
  getGridFS() {
    if (!this.isConnected || !this.gfs) {
      throw new Error('请先建立MongoDB连接');
    }
    return this.gfs;
  }

  /**
   * 获取数据库实例
   * @returns {Db} MongoDB数据库实例
   */
  getDB() {
    if (!this.isConnected || !this.db) {
      throw new Error('请先建立MongoDB连接');
    }
    return this.db;
  }

  /**
   * 关闭数据库连接
   */
  async disconnect() {
    if (this.client && this.isConnected) {
      await this.client.close();
      this.isConnected = false;
      this.db = null;
      this.gfs = null;
      console.log('MongoDB 连接已关闭');
    }
  }
}

module.exports = MongoDBGridFS;

关键优化点说明

  1. 去掉let that = this
    用async/await替代原来的回调函数写法,异步逻辑更清晰,同时自然保留了this的上下文,完全不需要额外的变量绑定。

  2. 解决"server instance in invalid state connected"错误

    • 加入单例模式:全局只维护一个连接实例,避免重复创建连接对象。
    • 添加isConnected状态标记:每次调用connect前先检查连接状态,已连接则直接返回,不会重复触发连接逻辑,从根源上避免了状态冲突。
    • 使用官方推荐的MongoClient.connect结合async/await:异步处理更可靠,避免回调嵌套导致的状态混乱。
  3. 优化Jest Mock的可行性

    • 类的结构模块化,每个方法职责单一,方便单独Mock。
    • 单例实例可以通过重置静态属性来测试不同场景,比如在测试前执行MongoDBGridFS.instance = null,确保每次测试都是全新的实例。

Jest Mock测试示例

下面是针对这个类的基础测试Mock示例,你可以根据实际需求扩展:

const MongoDBGridFS = require('./mongodb-gridfs');

describe('MongoDBGridFS 连接类测试', () => {
  // 每个测试前重置单例实例,保证测试独立性
  beforeEach(() => {
    MongoDBGridFS.instance = null;
  });

  it('连接成功后能获取GridFS实例', async () => {
    // Mock MongoClient.connect方法
    jest.mock('mongodb', () => ({
      MongoClient: {
        connect: jest.fn().mockResolvedValue({
          db: jest.fn().mockReturnValue({
            nativeMongo: {}
          })
        })
      }
    }));
    // Mock gridfs-stream库
    jest.mock('gridfs-stream', () => jest.fn().mockReturnValue({
      createWriteStream: jest.fn(),
      createReadStream: jest.fn()
    }));

    const dbInstance = new MongoDBGridFS();
    await dbInstance.connect('mongodb://localhost:27017', 'testDB');
    
    expect(dbInstance.isConnected).toBe(true);
    expect(dbInstance.getGridFS()).toBeDefined();
  });

  it('未连接时获取GridFS会抛出错误', () => {
    const dbInstance = new MongoDBGridFS();
    expect(() => dbInstance.getGridFS()).toThrow('请先建立MongoDB连接');
  });
});

使用建议

  • 应用启动时只需要调用一次connect,之后通过单例实例获取db或gfs即可,不要重复创建类实例。
  • 如果需要处理连接重试,可以在调用connect的地方添加重试逻辑(比如连接失败后间隔3秒重试,最多重试3次)。
  • 可以把GridFS的上传、下载等操作封装成类的方法,让这个类的功能更完整,也更便于测试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:36:37