使用jest mock函数无法覆写Hyperledger Fabric接口测试的invoke方法
问题核心原因
你当前的写法不生效的核心是函数引用被提前缓存:
- 你的服务端路由/控制器代码中,大概率是提前解构了
invoke方法(比如const { invoke } = require('./contracts.srv')),文件加载时就把原始函数的引用存在了本地变量中,后续你修改contractsSrv对象上的invoke属性,无法影响已经被拿走的独立引用。 - 测试代码的加载顺序错误:你先在
beforeEach中加载了server,完成了整个服务的初始化、所有依赖模块的加载,之后才在it块中修改invoke方法,此时修改已经不会对已经初始化完成的服务产生任何影响。
解决方案
方案1:调整业务代码调用方式 + 调整测试加载顺序
如果可以修改业务代码,优先用这个方案,逻辑更灵活,支持单用例自定义mock返回值。
- 首先修改控制器/路由中调用
invoke的写法,不要提前解构方法,每次调用都从contractsSrv对象上取最新属性:
// 原错误写法(提前缓存了函数引用) // const { invoke } = require('../services/contracts.srv'); // 调整为 const contractsSrv = require('../services/contracts.srv'); // 调用时写为 contractsSrv.invoke(参数)
- 调整测试代码的执行顺序,mock操作放在server加载之前,每次测试后清除模块缓存避免交叉影响:
'use strict'; require('dotenv').config(); const request = require('supertest'); const contractsSrv = require('../../../services/contracts.srv'); const config = require('config'); let server; let invokeSpy; jest.setTimeout(3000); describe('/api/contracts', () => { beforeEach(() => { // 先mock方法,再加载server invokeSpy = jest.spyOn(contractsSrv, 'invoke').mockResolvedValue({ data: { response: 'true', txID: config.get('TX_ID'), }, }); server = require('../../../server'); }); afterEach(() => { server.close(); // 恢复原始方法,清除模块缓存 invokeSpy.mockRestore(); jest.resetModules(); }); describe('post /', () => { it('return transaction record and id', async () => { const res = await request(server) .post('/api/contracts') .send({ field1: '001', field2: '002', }) .set('api-token', config.get('TOKEN')); expect(res.status).toBe(200); // 可新增断言验证invoke被正确调用 expect(invokeSpy).toHaveBeenCalledWith(expect.objectContaining({ field1: '001', field2: '002' })); }); }); });
方案2:顶部提前mock整个模块
如果不想修改业务代码,可以用jest.mock在文件最顶部提前mock模块,Jest会把jest.mock的执行逻辑提升到所有代码运行之前,保证模块第一次被加载时就是mock后的版本,不受业务代码解构的影响:
'use strict'; // 必须放在所有require语句之前 jest.mock('../../../services/contracts.srv', () => ({ ...jest.requireActual('../../../services/contracts.srv'), invoke: jest.fn().mockResolvedValue({ data: { response: 'true', txID: require('config').get('TX_ID'), }, }) })); require('dotenv').config(); const request = require('supertest'); const contractsSrv = require('../../../services/contracts.srv'); const config = require('config'); let server; jest.setTimeout(3000); describe('/api/contracts', () => { beforeEach(() => { server = require('../../../server'); }); afterEach(() => { server.close(); jest.resetAllMocks(); }); describe('post /', () => { it('return transaction record and id', async () => { const res = await request(server) .post('/api/contracts') .send({ field1: '001', field2: '002', }) .set('api-token', config.get('TOKEN')); expect(res.status).toBe(200); expect(contractsSrv.invoke).toHaveBeenCalled(); }); }); });
内容的提问来源于stack exchange,提问作者Matias Salimbene
相关产品推荐
相关产品推荐

