Express router模拟post请求handler未按预期运行问题排查求助
问题根因
- 模块加载顺序错误:你在
beforeEach中先执行require('../../../bin/www')启动服务时,Express 应用已经完成了原始documents.js路由的加载和挂载,后续测试用例中修改路由对象的post方法,不会对已经启动的服务产生任何影响。 - Mongoose 实例 mock 无效:你仅给测试用例中手动创建的
user实例挂载了leftDiskSpacemock 方法,但路由内部是通过User.findById从数据库重新查询得到全新的 User 实例,这个新实例没有你挂载的 mock 逻辑,因此 mock 不生效。
解决方案
方案1:mock 整个路由 post 方法(满足你测试 mock 路由是否被调用的需求)
使用 Jest 的jest.mock提前托管路由模块加载,必须放在所有require操作(尤其是启动服务的require('../../../bin/www'))之前:
// 放在测试文件最顶部 jest.mock('../../../routes/documents', () => { const express = require('express'); const mockRouter = express.Router(); mockRouter.post = jest.fn(); return mockRouter; }); const request = require('supertest'); const { Document } = require('../../../models/document'); const { User } = require('../../../models/user'); // 其余原有require内容
后续在测试用例中直接给documentsRouter.post添加自定义实现即可,此时 Express 挂载的就是你 mock 的路由对象,请求可以触发 mock 逻辑。
方案2:仅 mock User 方法(更符合你测试leftDiskSpace是否被调用的原始需求,无需修改路由)
你不需要 mock 整个路由,直接 mock 路由依赖的 User 模型方法即可,有两种实现方式:
方式A:mock User.findById返回值
让findById直接返回你提前配置好 mock 方法的用户实例:
// 在发送请求前添加 const mockLeftDiskSpace = jest.fn().mockRejectedValue(new Error('An error occured...')); jest.spyOn(User, 'findById').mockResolvedValue({ ...user._doc, // 保留原有user属性 leftDiskSpace: mockLeftDiskSpace, documents: user.documents, save: jest.fn().mockResolvedValue(user) // 可按需mock save方法 }); // 发送请求后断言 expect(mockLeftDiskSpace).toHaveBeenCalled();
方式B:直接 mock User 原型方法
所有 User 实例的自定义方法都挂载在原型上,直接 mock 原型方法可以全局生效:
// 在发送请求前添加 const mockLeftDiskSpace = jest.spyOn(User.prototype, 'leftDiskSpace').mockRejectedValue(new Error('An error occured...')); // 测试结束后恢复原始方法 afterEach(() => { mockLeftDiskSpace.mockRestore(); }); // 发送请求后断言 expect(mockLeftDiskSpace).toHaveBeenCalled();
注意事项
- 所有 mock 操作必须早于对应模块的加载时机,否则 mock 不会生效
- 如果做纯集成测试不需要 mock 路由逻辑,仅 mock 业务层依赖即可达到测试目的
- Supertest 发送的是真实 HTTP 请求,需要保证 Express 挂载的所有依赖都是你 mock 后的版本,否则 mock 逻辑不会被触发
内容的提问来源于stack exchange,提问作者M. Çağlar TUFAN
相关产品推荐
相关产品推荐

