如何测试柯里化函数中作为参数传入的非直接调用Mock方法?
解决Jest无法捕获柯里化Logger中console调用的问题
这个问题的核心原因其实很简单:你的logger模块在加载时就已经绑定了原始的console.log/console.info引用,而Jest的spyOn是在测试运行时才替换console方法的,这时候柯里化函数里保存的还是模块加载时的旧引用,自然测不到调用。
下面给你三种可行的解决方案,你可以根据自己的场景选择:
方案一:延迟获取console方法引用
修改logger的实现,让它每次调用日志方法时才去获取当前的console方法,而不是提前缓存引用:
// logger.ts // 把logFn改成一个返回函数的 getter,每次调用都取最新值 const log = (getLogFn: () => (...args: any[]) => void) => (message: any) => { getLogFn()(message); }; const logger = { debug: log(() => console.log), info: log(() => console.info), }; export default logger;
这样每次调用logger.debug()时,都会重新获取console.log的当前值,Jest的spy就能正常捕获到调用了。
方案二:测试前重置模块缓存并重新导入
利用Jest的模块缓存机制,在创建spy之后再重新加载logger模块,让它绑定被spy替换后的console方法:
// logger.spec.ts describe('logger', () => { describe('debug', () => { it('sets a text as log output', () => { // 先创建spy并替换console.log const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); // 清除已加载的logger模块缓存 jest.resetModules(); // 在spy生效后重新导入logger const logger = require('./logger').default; const text = 'Export is working properly'; logger.debug(text); expect(consoleSpy).toHaveBeenCalledTimes(1); expect(consoleSpy).toHaveBeenCalledWith(text); // 恢复原始console.log consoleSpy.mockRestore(); }); }); });
这种方法不需要修改业务代码,适合不想改动原有logger实现的场景。
方案三:使用依赖注入(最推荐)
把console作为依赖注入到logger中,这样测试时可以直接传入mock的console实例,完全不需要依赖Jest的spy机制,测试逻辑更清晰:
// logger.ts // 定义一个工厂函数,允许传入自定义的console实例 const createLogger = (consoleInstance = console) => { const log = (logFn: (...args: any[]) => void) => (message: any) => { logFn(message); }; return { debug: log(consoleInstance.log), info: log(consoleInstance.info), }; }; // 默认导出使用原生console的logger export default createLogger(); // 导出工厂函数供测试使用 export { createLogger };
然后测试代码可以这样写:
// logger.spec.ts import { createLogger } from './logger'; describe('logger', () => { describe('debug', () => { it('sets a text as log output', () => { // 创建一个mock的console对象 const mockConsole = { log: jest.fn(), info: jest.fn(), }; // 使用mock console创建logger实例 const logger = createLogger(mockConsole); const text = 'Export is working properly'; logger.debug(text); expect(mockConsole.log).toHaveBeenCalledTimes(1); expect(mockConsole.log).toHaveBeenCalledWith(text); }); }); });
这种方式不仅解决了测试问题,还让logger的扩展性更好——以后如果需要替换日志实现(比如输出到文件),只需要传入对应的console-like对象即可。
内容的提问来源于stack exchange,提问作者axel
相关产品推荐
相关产品推荐

