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

使用Jest模拟sqlite3实现时遇测试失败问题求助

问题描述

我在用Jest为sqlite3的实现编写测试时遇到了问题,目标是为attach方法编写测试,核心要验证db.prepare和db.prepare.run是否被传入正确参数调用。

attach方法代码

async attach(id: string): Promise<void> {
    await this.dbService.query((cb) => this.dbService.db.prepare(
      "UPDATE table SET boolean = true WHERE id = ?",
      id, cb).run()
    );
}

DBService的sqlite3实现

import sqlite3 from "sqlite3";

export class DBService {

  db: sqlite3.Database;

  private static instance: DBService | undefined = undefined;

  constructor() {
    this.db = new sqlite3.Database(`${someFolder}/somedb.db`);
  }

  public static getInstance(): DBService {
    if (!this.instance) {
      this.instance = new DBService();
    }
    return this.instance;
  }

  async query<T>(query: (cb: (error: Error | null, result: T | null) => void) => void): Promise<T> {
    return new Promise((resolve, reject) => {
      this.db.serialize(() => {
        query((err: Error | null, res: T | null) => {
          if (err) {
            console.log("query error: ", err);
            reject(err);
          } else {
            resolve(res as T);
          }
        });
      });
    });
  }
}

测试代码及问题

测试运行失败,提示找不到被模拟的prepare方法,我的测试代码如下:

jest.mock("sqlite3");

jest.mock("./services/database/DBService.ts", () => {
  const mockInstance = {
    query: jest.fn(),
    db: {
      prepare: jest.fn()
    }
  };
  return {
    DBService: {
      getInstance: jest.fn(() => mockInstance),
    }
  };
});

beforeEach(() => {
  mockQuery = DBService.getInstance().query as jest.Mock;
  const dbMock = {
    prepare: jest.fn(() => ({
      run: jest.fn(),
    })),
  };

  // Mock sqlite3.Database constructor
  (sqlite3.Database as jest.Mock).mockImplementation(() => dbMock);
  service = SyncService.getInstance();
});

await service.attach(1);

expect(mockQuery).toHaveBeenCalledTimes(1);
expect(dbMock.prepare).toHaveBeenCalledWith(
  "UPDATE table SET boolean = true WHERE id = ?",
  id,
  expect.any(Function)
);
expect(runMock).toHaveBeenCalledTimes(1);

解决方案

测试失败的核心原因是双重模拟冲突:你同时mock了DBService实例和sqlite3.Database构造函数,但attach方法直接调用的是DBService单例中的db对象,而非通过sqlite3.Database新创建的实例。此外测试代码还存在变量未定义、未手动触发Promise回调等问题。

修正后的测试代码如下:

// 先mock sqlite3,避免加载实际模块
jest.mock('sqlite3');

// 重点:在DBService的mock中直接定义prepare和run的mock,并暴露引用
jest.mock('./services/database/DBService.ts', () => {
  const mockRun = jest.fn();
  const mockPrepare = jest.fn(() => ({ run: mockRun }));
  const mockInstance = {
    query: jest.fn((queryFn) => {
      // 手动调用query传入的回调,触发内部逻辑并resolve Promise
      queryFn(null, null);
      return Promise.resolve();
    }),
    db: { prepare: mockPrepare }
  };
  return {
    DBService: {
      getInstance: jest.fn(() => mockInstance),
      // 暴露mock函数方便测试断言
      __mocks: { mockPrepare, mockRun }
    }
  };
});

describe('SyncService.attach', () => {
  let service: SyncService;
  let mockQuery: jest.Mock;
  let mockPrepare: jest.Mock;
  let mockRun: jest.Mock;

  beforeEach(() => {
    // 从mock的DBService中获取各个mock函数
    const dbServiceMock = DBService.getInstance() as any;
    mockQuery = dbServiceMock.query;
    mockPrepare = DBService.__mocks.mockPrepare;
    mockRun = DBService.__mocks.mockRun;

    // 重置所有mock的调用记录,避免测试间干扰
    jest.clearAllMocks();

    service = SyncService.getInstance();
  });

  it('should call db.prepare and run with correct parameters', async () => {
    const testId = '123';
    await service.attach(testId);

    // 验证query被调用一次
    expect(mockQuery).toHaveBeenCalledTimes(1);

    // 验证prepare的参数:SQL语句、测试ID、回调函数
    expect(mockPrepare).toHaveBeenCalledWith(
      'UPDATE table SET boolean = true WHERE id = ?',
      testId,
      expect.any(Function)
    );

    // 验证run方法被调用一次
    expect(mockRun).toHaveBeenCalledTimes(1);
  });
});

关键修正说明

  1. 统一mock对象:直接在DBService的mock中定义prepare和run的mock函数,避免和sqlite3的mock冲突——因为DBService是单例,attach方法不会重新创建数据库实例。
  2. 手动触发Promise回调:DBService.query内部用Promise封装逻辑,mock时需要手动调用传入的queryFn,否则attach方法会一直等待Promise完成。
  3. 暴露mock引用:将mockPrepare和mockRun挂载到DBService.__mocks下,方便测试中直接获取并断言。
  4. 重置mock状态:在beforeEach中调用jest.clearAllMocks(),确保每个测试用例的mock调用记录都是独立的。
  5. 修复变量问题:替换测试中未定义的id和runMock,使用明确的测试变量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 01:40:56