Jest+TypeScript:如何模拟better-sqlite3中的方法?
解决better-sqlite3测试中的undefined报错与只读属性问题
核心问题分析
- 模块级变量初始化时机冲突:
getAllPlayersDb是在db-controllers.ts模块加载时就执行了db.prepare,但Jest的mock是在测试启动后才生效,导致getAllPlayersDb拿到的是未正确配置的mock返回值(undefined),调用all()时触发Cannot read properties of undefined错误。 - 只读属性无法直接修改:
better-sqlite3的db.open是只读getter属性,直接赋值或用spyOn修改都会报错,因为该属性不可配置。 - 固定mock无法适配多场景:单一的
preparemock无法满足不同SQL查询的返回需求。
解决方案
1. 重置模块+动态加载
通过jest.resetModules()在每个测试前重置模块缓存,再用require动态加载控制器和DB模块,确保每次测试都能基于最新的mock配置初始化模块级变量。
2. 模拟只读getter属性
在mock DB对象时,用ES6的getter语法模拟open属性,避免直接赋值只读属性的错误。
3. 动态配置prepare的mock行为
在加载模块前配置db.prepare的mock返回值,让模块级的getAllPlayersDb能拿到带有all方法的正确对象。
修改后的测试代码
const fakeTables = { players: [/* 填入你的测试玩家数据 */] }; describe("Database controllers tests: ", () => { afterAll(() => { jest.resetAllMocks(); }); describe("Test getDbStatus()", () => { beforeEach(() => { jest.resetModules(); // 重置模块缓存,确保每次测试加载新实例 }); it("Should return online status if DB is online", async () => { // 先mock DB,模拟open为在线状态 jest.mock("../../db", () => ({ db: { get open() { return true; }, prepare: jest.fn() } })); // 动态加载控制器 const { getDbStatus } = require("../../controllers/db-controllers"); const dbStatus = await getDbStatus(); expect(dbStatus).toEqual({ message: `Database is online`, isOnline: true, }); }); it("Should return offline status if DB is offline", async () => { jest.mock("../../db", () => ({ db: { get open() { return false; }, prepare: jest.fn() } })); const { getDbStatus } = require("../../controllers/db-controllers"); const dbStatus = await getDbStatus(); expect(dbStatus).toEqual({ message: `Database is offline`, isOnline: false, }); }); }); describe("Test getAllPlayersFromDB()", () => { beforeEach(() => { jest.resetModules(); }); it("Should return all players", async () => { // mock prepare返回带有all方法的对象,模拟查询结果 jest.mock("../../db", () => ({ db: { open: jest.fn(), prepare: jest.fn().mockReturnValue({ all: jest.fn().mockReturnValue(fakeTables.players) }) } })); // 加载控制器,此时模块级的getAllPlayersDb已正确初始化 const { getAllPlayersFromDB } = require("../../controllers/db-controllers"); expect(await getAllPlayersFromDB()).toEqual(fakeTables.players); // 验证prepare是否被传入正确的SQL语句 const { db } = require("../../db"); expect(db.prepare).toHaveBeenCalledWith("SELECT * FROM player"); }); }); });
扩展:适配多查询场景
如果需要测试不同SQL语句的返回结果,只需在对应测试中修改prepare的mock逻辑即可:
it("Should return specific player by id", async () => { const fakePlayer = { id: 1, name: "TestPlayer" }; jest.mock("../../db", () => ({ db: { prepare: jest.fn((sql) => { // 根据传入的SQL语句返回不同的mock方法 if (sql === "SELECT * FROM player WHERE id = ?") { return { get: jest.fn().mockReturnValue(fakePlayer) }; } return { all: jest.fn() }; }) } })); const { getPlayerByIdFromDB } = require("../../controllers/db-controllers"); expect(await getPlayerByIdFromDB(1)).toEqual(fakePlayer); });
内容的提问来源于stack exchange,提问作者Armax7
相关产品推荐
相关产品推荐

