如何重置Lodash的once()函数以避免单元测试污染?
如何在单元测试间重置Lodash的
once函数? 我正在为一个计算耗时较长的函数编写自动化测试,需要确保结果被缓存。Lodash的once函数能满足需求,但在单元测试中,once无法在测试间重置,会引发测试污染。
请问是否有办法在测试间重置once,使其每次测试开始时都像首次调用一样?以下是once的源码:
function once(func) { return before(2, func); } function before(n, func) { var result; if (typeof func != 'function') { throw new TypeError(FUNC_ERROR_TEXT); } n = toInteger(n); return function() { if (--n > 0) { result = func.apply(this, arguments); } if (n <= 1) { func = undefined; } return result; }; }
顺带一提,我可以通过不使用once、自行实现一次性缓存,或打猴子补丁/桩函数来解决问题,但我出于学术目的提问:若有其他重置once的方法,能让我掌握新的实用技巧。
方法1:访问非标准的函数作用域链(仅学术研究用)
Lodash的once基于before实现,返回的函数会闭包n、result、func三个变量。在V8引擎(Chrome、Node.js等)中,可以通过非标准的作用域链API访问并修改这些闭包变量,实现重置:
// 假设`onceWrapped`是你用`_.once(originalFunc)`生成的函数 const scope = onceWrapped.__proto__.__parent__; // 重置闭包变量到初始状态 scope.n = 2; // 因为once本质是before(2, func) scope.result = undefined; scope.func = originalFunc; // 传入你最初给once的原始函数
注意:这个方法依赖V8的私有实现,不属于ECMAScript标准,跨引擎兼容性差,仅适合测试或学术研究场景,绝对不要在生产环境使用。
方法2:给once包装的函数添加重置钩子
基于上面的作用域访问技巧,我们可以给once生成的函数封装一个reset方法,方便测试时调用:
function createResettableOnce(func) { const wrapped = _.once(func); const scope = wrapped.__proto__.__parent__; // 添加重置方法 wrapped.reset = () => { scope.n = 2; scope.result = undefined; scope.func = func; }; return wrapped; } // 使用示例 const myFunc = () => { /* 耗时计算逻辑 */ }; const resettableFunc = createResettableOnce(myFunc); // 在测试的beforeEach中重置 beforeEach(() => { resettableFunc.reset(); });
方法3:利用测试框架的模块隔离能力
如果使用Jest、Mocha等支持模块隔离的测试框架,可以通过重置模块的方式让once每次测试都重新生成:
比如在Jest中:
// 在测试文件顶部 let resettableFunc; beforeEach(() => { jest.resetModules(); // 重置所有已加载的模块 const _ = require('lodash'); const myFunc = require('./myFunc'); // 重新加载你的函数模块 resettableFunc = _.once(myFunc); });
这种方法不需要修改闭包,而是通过模块重新加载,让每个测试都拿到全新的once包装函数,避免测试污染。
内容的提问来源于stack exchange,提问作者Daniel Kaplan
相关产品推荐
相关产品推荐

