
本文介绍通过函数表达式替代函数声明的方式,在不修改原始逻辑的前提下,安全地覆盖全局函数以支持单元测试中的行为模拟。
在 JavaScript 单元测试中,常需对依赖的底层函数(如 getData)进行模拟(mock),以隔离被测函数(如 getUsers)的行为。但若目标函数是使用函数声明(function getData() { ... })定义的,因其存在提升(hoisting) 且绑定在作用域顶层,直接赋值覆盖(如 getData = mock_getData)在严格模式下可能无效,或因执行时机问题导致未生效。
✅ 正确做法是:统一使用函数表达式定义可模拟的全局函数。这样它们就成为可重赋值的变量,便于在测试前动态替换:
// ✅ 推荐:用函数表达式定义,而非函数声明
var getData = function() {
// 实际逻辑:从数据库获取数据
throw new Error('Real database call — should not run in tests');
};
var getUsers = function() {
var data = getData(); // 调用的是当前绑定的 getData
return data.map(user => ({ ...user, loaded: true }));
};
function main() {
var users = getUsers();
// ...
}随后,在测试入口处(如 runTests 前),通过简单赋值即可完成模拟:
// ? 测试前:覆盖 getData 为模拟实现
var getData = function() {
return [{ id: 0, name: 'Alice' }, { id: 1, name: 'Bob' }];
};
test('getUsers', function() {
var users = getUsers(); // 现在调用的是模拟版 getData
myAssert(users.length, 2);
myAssert(users[0].name, 'Alice');
myAssert(users[1].name, 'Bob');
});? 关键优势:
立即学习“Java免费学习笔记(深入)”;
- 无需引入外部测试框架(如 Jest 的 jest.mock);
- 零侵入原始业务代码(仅需将 function xxx() {...} 改为 var xxx = function() {...});
- 模拟与真实实现共用同一标识符,语义清晰、维护成本低;
- 支持按需开关(例如结合 _DEBUG = true 统一控制)。
⚠️ 注意事项:
- 所有被模拟的函数必须定义为可变变量(var / let),不可用 const;
- 模拟赋值必须在被测函数首次调用之前执行(推荐放在 runTests() 开头);
- 若模块化环境(ESM)中,全局变量不可写,此时应改用依赖注入或代理模式;
- 生产构建时建议通过打包工具(如 Webpack DefinePlugin)自动移除模拟逻辑,避免泄露。
总结:函数表达式 + 可变变量绑定,是最轻量、最可控的原生 JavaScript 函数模拟方案,特别适合微型测试库或教学/原型场景。










