Jest spyOn.mockImplementation调用原方法 如何按测试用例自定义mock
问题原因
直接使用jest.spyOn无法替换中间件实现的核心原因是:
- 测试文件导入
app/路由实例时,路由初始化阶段就已经拿到了verifyUser的原始函数引用 - 后续
spyOn只是修改了中间件模块的导出属性指向,不会更新路由已经持有的原始函数引用,所以请求进入时还是执行真实逻辑 - 顶层的
jest.mock会被Jest自动提升到所有导入逻辑之前执行,所有引入该中间件的代码拿到的都是mock后的函数引用,所以可以正常生效,但写死实现就没法给不同用例配置不同逻辑。
可落地的实现方案
方案1:顶层空mock + 单测内动态配置实现(推荐)
这个方案适配绝大多数场景,配置简单,用例之间隔离性好:
- 首先在测试文件顶部声明模块mock,不要写死实现,只生成空的mock函数,Jest会自动把这段逻辑提升到所有import语句之前执行:
// jest.mock 写在import区域即可,不需要手动挪到最顶部,Jest会自动提升执行优先级 jest.mock('../../middlewares/auth.middleware'); import * as authMiddleware from '../../middlewares/auth.middleware'; import request from 'supertest'; // 剩余其他导入:app实例、其他业务依赖等
- 新增统一的重置逻辑,避免不同用例的mock实现、调用记录互相污染:
beforeEach(() => { // 每个用例执行前清空所有mock的状态 jest.resetAllMocks(); });
- 在不同测试用例中,按需给
verifyUser编写对应的mock逻辑即可:
describe('POST /api/users', () => { test('用户仅在外部鉴权系统存在时返回200', async () => { // 当前用例的mock逻辑:给req挂载合法用户信息,放行请求 (authMiddleware.verifyUser as jest.Mock).mockImplementation( async (req, _res, next) => { req.user = { id: '1', email: 'user1@email.com', fullName: null, }; next(); } ); const res = await request(app) .post('/api/users') .send({ fullName: 'Jimmy Neutron' }); expect(res.status).toEqual(200); }); test('鉴权失败时返回401', async () => { // 另一个用例的mock逻辑:直接返回401未授权 (authMiddleware.verifyUser as jest.Mock).mockImplementation( async (_req, res, _next) => { res.status(401).json({ error: 'You are not authorized to make this request' }); } ); const res = await request(app).post('/api/users').send({}); expect(res.status).toEqual(401); }); });
方案2:需要保留真实中间件逻辑的场景
如果部分用例需要走真实的verifyUser逻辑,部分需要mock,可以在顶层mock时引入原始模块实现,默认走真实逻辑,需要mock时再覆盖:
// 顶部mock写法,默认保留真实实现 jest.mock('../../middlewares/auth.middleware', () => { const actualModule = jest.requireActual('../../middlewares/auth.middleware'); return { ...actualModule, // 给真实函数套一层jest.fn,既可以默认走原逻辑,也支持后续mockImplementation覆盖 verifyUser: jest.fn(actualModule.verifyUser) }; });
这种写法下,默认所有用例都会执行真实的鉴权逻辑,需要mock的用例里直接调用mockImplementation覆盖即可,需要切回真实逻辑时调用(authMiddleware.verifyUser as jest.Mock).mockRestore()就行。
注意点
- 不要在用例内部单独调用
spy.mockRestore()之后直接复用同一个spy实例,容易导致后续用例拿到原始引用不生效,统一用beforeEach做重置更稳妥 - 如果使用TypeScript,记得给导入的中间件方法加
jest.Mock类型断言,避免TS类型报错
内容的提问来源于stack exchange,提问作者user1354934
相关产品推荐
相关产品推荐

