使用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); }); });
关键修正说明
- 统一mock对象:直接在
DBService的mock中定义prepare和run的mock函数,避免和sqlite3的mock冲突——因为DBService是单例,attach方法不会重新创建数据库实例。 - 手动触发Promise回调:
DBService.query内部用Promise封装逻辑,mock时需要手动调用传入的queryFn,否则attach方法会一直等待Promise完成。 - 暴露mock引用:将
mockPrepare和mockRun挂载到DBService.__mocks下,方便测试中直接获取并断言。 - 重置mock状态:在
beforeEach中调用jest.clearAllMocks(),确保每个测试用例的mock调用记录都是独立的。 - 修复变量问题:替换测试中未定义的
id和runMock,使用明确的测试变量。
内容的提问来源于stack exchange,提问作者neukoellnjenny
相关产品推荐
相关产品推荐

