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

Jest+TypeScript:如何模拟better-sqlite3中的方法?

解决better-sqlite3测试中的undefined报错与只读属性问题

核心问题分析

  1. 模块级变量初始化时机冲突:getAllPlayersDb是在db-controllers.ts模块加载时就执行了db.prepare,但Jest的mock是在测试启动后才生效,导致getAllPlayersDb拿到的是未正确配置的mock返回值(undefined),调用all()时触发Cannot read properties of undefined错误。
  2. 只读属性无法直接修改:better-sqlite3的db.open是只读getter属性,直接赋值或用spyOn修改都会报错,因为该属性不可配置。
  3. 固定mock无法适配多场景:单一的prepare mock无法满足不同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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 17:11:02