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

Jest中mockResolvedValueOnce不生效,如何正确模拟insertOne?

解决Jest模拟MongoDB insertOne时的insertedId解构错误

问题描述

我实现了一个可正常运行的MongoDB数据插入函数insertOne:

export const insertOne = async (table, data) => {
  try {
    const client = await getClient();
    const db = client.db(process.env.MONGO_DATABASE);
    const collection = db.collection(table);
    const { insertedId } = await collection.insertOne({
      ...data,
      createdAt: moment().tz("America/Mexico_City").toISOString(),
      updatedAt: moment().tz("America/Mexico_City").toISOString(),
    });
    const insertedData = await collection.findOne({ _id: insertedId });
    return insertedData;
  } catch (error) {
    mwLogger('error', 'Error inserting one to the database :', {
      name: error?.name || 'Unknown Error',
      message: error?.message || 'No message provided',    
      code: error?.code || 'No error code',  
      stack: error?.stack || 'No stack trace available' 
    });
    throw new Error('Database insertion failed');
  }
}

但用Jest编写单元测试时,mockCollection.insertOne.mockResolvedValueOnce({ insertedId })无法正确模拟返回值,报错TypeError: Cannot read properties of undefined (reading 'insertedId')。测试代码如下:

describe('insertOne', () => {

    beforeEach(() => {
      // Mock db and collection setup
      mockDb = { collection: jest.fn() };
      mockCollection = { insertOne: jest.fn(), findOne: jest.fn() };
      mockClient = { db: jest.fn().mockReturnValue(mockDb) };

      // Mock MongoClient.connect to return mockClient
      MongoClient.connect.mockResolvedValue(mockClient);

      // Mock collection method to return mockCollection
      mockDb.collection.mockReturnValue(mockCollection);
    });

    it('should insert data correctly and return the inserted document', async () => {
      const mockData = { name: 'John Doe' };
  
      // Mock the insertOne method to resolve with insertedId
      const insertedId = '123';
      mockCollection.insertOne.mockResolvedValueOnce({ insertedId });
  
      // Mock findOne to return the inserted data with timestamps
      const mockInsertedData = {
        _id: insertedId,
        ...mockData,
        createdAt: expect.any(String),
        updatedAt: expect.any(String),
      };
      mockCollection.findOne.mockResolvedValueOnce(mockInsertedData);
  
      const result = await insertOne('users', mockData);
  
      // Assertions
      expect(mockCollection.insertOne).toHaveBeenCalledWith(
        expect.objectContaining({
          ...mockData,
          createdAt: expect.any(String),
          updatedAt: expect.any(String),
        })
      );
      expect(result).toEqual(mockInsertedData);
    });
  });

解决方案

报错的核心原因是未正确mock getClient函数:原函数通过getClient()获取数据库客户端,但测试仅mock了MongoClient.connect,若getClient是封装MongoClient.connect的自定义函数,测试框架无法自动关联该mock,导致getClient()返回undefined,后续client.db()调用失败,最终collection.insertOne()变成调用undefined的方法,返回值为undefined,解构insertedId自然报错。

具体修正步骤

  1. Mock getClient函数
    根据getClient的导出方式选择对应mock方式:

    • 若getClient与insertOne同文件:
      // 测试文件顶部引入模块
      import * as dbModule from './你的数据库模块路径';
      // 在beforeEach中mock getClient
      jest.spyOn(dbModule, 'getClient').mockResolvedValue(mockClient);
      
    • 若getClient是单独模块:
      // 测试文件顶部直接mock整个模块
      jest.mock('./getClient模块路径', () => ({
        getClient: jest.fn().mockResolvedValue(mockClient)
      }));
      
  2. 完善beforeEach逻辑
    将mock getClient的代码加入beforeEach,确保每个测试用例都能获取正确的mock客户端,同时补充环境变量mock:

    beforeEach(() => {
      // 初始化mock对象
      mockDb = { collection: jest.fn() };
      mockCollection = { insertOne: jest.fn(), findOne: jest.fn() };
      mockClient = { db: jest.fn().mockReturnValue(mockDb) };
    
      // 关键:mock getClient返回预设的mockClient
      jest.spyOn(dbModule, 'getClient').mockResolvedValue(mockClient);
      // Mock数据库环境变量
      process.env.MONGO_DATABASE = 'test-db';
      // 保持collection的mock逻辑
      mockDb.collection.mockReturnValue(mockCollection);
    });
    
  3. 添加用例清理逻辑
    在afterEach中清除mock和环境变量,避免测试用例间相互污染:

    afterEach(() => {
      jest.restoreAllMocks();
      delete process.env.MONGO_DATABASE;
    });
    

修正后的完整测试示例

import * as dbModule from './你的数据库模块路径';
import { insertOne } from './你的数据库模块路径';

describe('insertOne', () => {
  let mockDb;
  let mockCollection;
  let mockClient;

  beforeEach(() => {
    // 初始化mock对象
    mockDb = { collection: jest.fn() };
    mockCollection = { insertOne: jest.fn(), findOne: jest.fn() };
    mockClient = { db: jest.fn().mockReturnValue(mockDb) };

    // Mock getClient返回预设客户端
    jest.spyOn(dbModule, 'getClient').mockResolvedValue(mockClient);
    // Mock环境变量
    process.env.MONGO_DATABASE = 'test-db';
    // Mock collection返回mockCollection
    mockDb.collection.mockReturnValue(mockCollection);
  });

  afterEach(() => {
    // 清除mock,避免用例间污染
    jest.restoreAllMocks();
    delete process.env.MONGO_DATABASE;
  });

  it('should insert data correctly and return the inserted document', async () => {
    const mockData = { name: 'John Doe' };
    const insertedId = '123';

    // Mock insertOne返回结果
    mockCollection.insertOne.mockResolvedValueOnce({ insertedId });
    // Mock findOne返回插入的数据
    const mockInsertedData = {
      _id: insertedId,
      ...mockData,
      createdAt: expect.any(String),
      updatedAt: expect.any(String),
    };
    mockCollection.findOne.mockResolvedValueOnce(mockInsertedData);

    const result = await insertOne('users', mockData);

    // 验证insertOne的调用参数
    expect(mockCollection.insertOne).toHaveBeenCalledWith(
      expect.objectContaining({
        ...mockData,
        createdAt: expect.any(String),
        updatedAt: expect.any(String),
      })
    );
    // 验证返回结果
    expect(result).toEqual(mockInsertedData);
    // 验证getClient被调用
    expect(dbModule.getClient).toHaveBeenCalled();
    // 验证db方法被传入正确的数据库名
    expect(mockClient.db).toHaveBeenCalledWith('test-db');
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 05:07:21