使用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
相关产品推荐
相关产品推荐

