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

使用Jest测试Express多路由时Mock函数出现undefined问题

问题根因

你的报错本质是mock调用次数和实际调用次数不匹配:

  • 两个控制器的顶层代码都执行了const db = getDbConnection(),也就是说只要app.ts被导入,就会先后触发两次getDbConnection调用
  • 你在测试的beforeEach里只给mock的getDbConnection设置了mockReturnValueOnce,仅能满足第一次调用的返回值,第二次调用会默认返回undefined,排在后面导入的控制器拿到的db就是undefined,自然调用query方法就会报错
  • 修改控制器导入顺序后报错的测试也跟着变,就是因为第二个导入的控制器永远拿不到mock返回的db实例
修复方案

方案1:直接修改测试mock逻辑(最快修复,无需改动业务代码)

把测试里的mockReturnValueOnce改成mockReturnValue,匹配两次控制器初始化的调用:

// tests/one.ts 和 tests/two.ts 的 beforeEach 都调整如下
beforeEach(() => {
  jest.resetModules(); // 每次用例执行前清空模块缓存,避免用例间缓存干扰
  const mockedQueryFn = {
    query: jest.fn().mockResolvedValue(data),
  };
  // 不管调用多少次都返回同一个mock实例
  mockedDB.mockReturnValue(mockedQueryFn);
});

afterAll(() => {
  jest.restoreAllMocks();
});

方案2:优化业务代码结构(更符合生产最佳实践)

当前控制器在顶层初始化数据库连接的写法不合理,模块加载阶段如果数据库连接失败会直接导致服务启动失败,建议改成惰性获取连接:

// controllers/one.ts 和 controllers/two.ts 都做如下修改
import getDbConnection from "../db";
import * as express from "express";

const router = express.Router();

// 删掉顶层的db初始化,放到请求处理函数里
router.get("/", async (_, res) => {
  const db = getDbConnection(); // 请求进来的时候再获取连接
  const sql = "SELECT * from table";
  const result = await db.query(sql);
  res.status(200).json(result);
});

export default router;

修改后getDbConnection只会在请求处理的时候才被调用,不会在模块导入阶段被提前消费,原来的测试代码不用改也能正常运行。

方案3:拆分单测和集成测试(更规范的测试思路)

如果是做控制器单元测试,不需要引入整个app和supertest,直接测试路由处理函数逻辑即可:

// 示例:controller one 单测
import controllerOne from "../src/controllers/one";
import getDbConnection from "../src/db";

jest.mock("../src/db");

const mockRes = {
  status: jest.fn().mockReturnThis(),
  json: jest.fn()
} as any;

it("should return data", async () => {
  const mockDb = { query: jest.fn().mockResolvedValue(["data"]) };
  (getDbConnection as jest.Mock).mockReturnValue(mockDb);
  // 拿到路由处理函数执行,传入mock的req、res
  await controllerOne.handle({ method: "GET", path: "/" }, mockRes);
  expect(mockDb.query).toHaveBeenCalledWith("SELECT * from table");
  expect(mockRes.status).toHaveBeenCalledWith(200);
  expect(mockRes.json).toHaveBeenCalledWith(["data"]);
});

如果是做接口集成测试,可以全局mock db模块,所有测试用例共享同一个mock实例,无需每个用例单独设置返回值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 18:30:05