如何使用Jest创建可被识别为async函数的mock/spy函数?
问题原因
- 业务代码中的async函数校验逻辑存在两处缺陷:第一是变量引用错误,函数入参直接定义为
consumerFunction,校验时却引用了未定义的args.consumerFunction;第二是判断逻辑不成立,async函数本质是AsyncFunction类型的函数对象,只有执行后才会返回Promise,直接读取函数本身的.then属性永远为undefined,无法正确识别async函数。 jest.fn()默认生成的是普通Function类型的mock函数,即便传入async函数作为实现,外层包装的mock函数本身不属于AsyncFunction类型,若通过构造器类型判断是否为async函数,这类mock会被判定为非法。
解决方案
方案1:生成可通过AsyncFunction类型校验的Jest mock(无需修改业务代码)
通过修改mock函数原型链的方式,可以让jest生成的mock函数被识别为原生async函数,同时保留全部jest mock能力,封装方法如下:
const AsyncFunction = (async () => {}).constructor; const createAsyncMock = (impl) => { const mockFn = jest.fn(async (...args) => impl?.(...args)); Object.setPrototypeOf(mockFn, AsyncFunction.prototype); return mockFn; }
使用时替换原有jest.fn调用即可:
beforeEach(()=> { mockAsyncConsumerFunctionSpy = createAsyncMock((recordBody) => `${recordBody} - resolved consumer`); logSpy = jest.spyOn(console, 'log').mockImplementation(() => null); })
生成的mock函数可正常通过函数类型校验、async函数类型校验,同时支持所有jest mock的断言方法与属性访问,完全满足测试需求。
方案2:优化业务校验逻辑(工程实践层面更推荐)
业务逻辑中强制要求入参必须是async声明的函数没有实际价值:await关键字兼容所有返回thenable对象(带.then方法的Promise/类Promise实例)的函数,无论函数是async声明,还是普通函数手动返回Promise,执行效果完全一致。原有强制校验async类型的逻辑兼容性很差,会拦截所有合法的Promise返回函数、测试mock函数。
建议将校验逻辑调整为校验函数执行后的返回值是否为Promise即可,修改后的业务代码如下:
exports.consumeRecords = async (consumerFunction, records) => { if (!consumerFunction) { throw new Error(`Missing required argument, consumerFunction, in consumeRecords function`) } if (typeof consumerFunction !== 'function') { throw new Error(`Argument, consumerFunction, is not of type 'function' in consumeRecords function. Type found: ${typeof consumerFunction}`) } const executeResult = consumerFunction(records); if (!executeResult || typeof executeResult.then !== 'function') { throw new Error(`Argument, consumerFunction, must return a Promise when called in consumeRecords function.`) } await executeResult; // ... 剩余业务逻辑 }
调整后原有jest.fn(async () => {})写法的普通mock可以直接使用,不需要额外处理,同时业务代码对入参的兼容性也会显著提升。
内容的提问来源于stack exchange,提问作者lowcrawler
相关产品推荐
相关产品推荐

