如何创建可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;
关键优化点说明
去掉
let that = this
用async/await替代原来的回调函数写法,异步逻辑更清晰,同时自然保留了this的上下文,完全不需要额外的变量绑定。解决"server instance in invalid state connected"错误
- 加入单例模式:全局只维护一个连接实例,避免重复创建连接对象。
- 添加
isConnected状态标记:每次调用connect前先检查连接状态,已连接则直接返回,不会重复触发连接逻辑,从根源上避免了状态冲突。 - 使用官方推荐的
MongoClient.connect结合async/await:异步处理更可靠,避免回调嵌套导致的状态混乱。
优化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
相关产品推荐
相关产品推荐

