
在进行单元测试时,我们经常需要模拟(mock)某些函数或模块的行为,以隔离测试目标。然而,当一个模块中的函数(例如 senddatahandler)在内部调用了同一个模块中的另一个函数(例如 sendtoeh)时,直接在测试文件中对 app.sendtoeh 进行 jest.fn() 模拟,往往无法达到预期效果。
问题现象: 假设我们有一个 app 模块,其中包含 sendDataHandler 和 sendToEH 两个函数。sendDataHandler 内部会调用 sendToEH。在测试中,我们尝试通过 app.sendToEH = jest.fn() 来模拟 sendToEH 的行为,然后调用 app.sendDataHandler。
// 假设 app.js 模块内部结构类似
// var sendToEH = function() { /* 原始实现 */ };
// var sendDataHandler = function() { sendToEH(); }; // 内部调用
// 测试文件中的尝试
app.sendToEH = jest.fn(); // 模拟 app 对象上的 sendToEH
await app.sendDataHandler(req, res, next); // 调用 sendDataHandler
expect(app.sendToEH).toHaveBeenCalled(); // 期望模拟函数被调用在这种情况下,测试会失败。原因是当 sendDataHandler 被导入并执行时,它会从其原始定义所在的模块作用域中查找 sendToEH 的引用,而不是我们后来在测试文件中对 app 对象属性进行的模拟。换句话说,sendDataHandler 内部引用的 sendToEH 与 app.sendToEH 已经不是同一个实例。即使 app.sendToEH 被成功模拟,sendDataHandler 调用的仍然是 sendToEH 的原始实现。
为了解决这个问题,核心思想是确保 sendDataHandler 内部调用的 sendToEH 与我们在测试中模拟的 sendToEH 引用的是同一个实例。这可以通过将这些相关函数封装在一个对象中并导出该对象来实现。
模块结构调整: 将 sendToEH 和 sendDataHandler 作为同一个导出对象的属性。这样,sendDataHandler 在内部调用 sendToEH 时,会通过该导出对象来访问它,从而保证了引用的统一性。
// 模块文件 (例如:app.js)
var sendToEH = function sendToEH() {
console.log('Original sendToEH called');
// ... 实际的 sendToEH 逻辑
};
var sendDataHandler = function sendDataHandler() {
console.log('sendDataHandler called');
// 内部通过 exportFunctions 对象访问 sendToEH
exportFunctions.sendToEH();
};
const exportFunctions = {
sendToEH,
sendDataHandler
};
export default exportFunctions; // 导出包含所有函数的对象测试代码实现: 在测试文件中,我们导入这个 exportFunctions 对象(这里假设为 app 变量),然后直接模拟 app.exportFunctions.sendToEH。当 app.exportFunctions.sendDataHandler 被调用时,它内部对 exportFunctions.sendToEH() 的调用将命中我们设置的模拟函数。
// 测试文件 (例如:app.test.js)
import app from './app'; // 导入包含 exportFunctions 的模块
describe('sendDataHandler functionality', () => {
let originalSendToEH;
beforeEach(() => {
// 备份原始函数,以防需要恢复或避免影响其他测试
originalSendToEH = app.sendToEH;
// 模拟 app.sendToEH,现在 sendDataHandler 内部会调用到这个模拟
app.sendToEH = jest.fn();
});
afterEach(() => {
// 恢复原始函数,确保测试隔离
app.sendToEH = originalSendToEH;
jest.restoreAllMocks(); // 恢复所有模拟
});
test('sendDataHandler should call sendToEH', async () => {
// 模拟请求和响应对象
const req = {};
const res = {};
const next = jest.fn();
await app.sendDataHandler(req, res, next);
// 验证模拟函数是否被调用
expect(app.sendToEH).toHaveBeenCalledTimes(1);
// 可以进一步验证调用参数
// expect(app.sendToEH).toHaveBeenCalledWith(/* 期望的参数 */);
});
test('sendDataHandler should call sendToEH with specific arguments', async () => {
app.sendToEH.mockImplementation(() => {
console.log('Mocked sendToEH called');
});
const req = { data: 'test data' };
const res = {};
const next = jest.fn();
await app.sendDataHandler(req, res, next);
expect(app.sendToEH).toHaveBeenCalled();
// 假设 sendDataHandler 内部会传递 req.data 给 sendToEH
// expect(app.sendToEH).toHaveBeenCalledWith(req.data);
});
});通过这种方式,我们确保了 sendDataHandler 内部调用的 sendToEH 和我们在测试中模拟的 app.sendToEH 指向了内存中的同一个函数引用。当 app.sendToEH 被 jest.fn() 覆盖时,sendDataHandler 通过 exportFunctions.sendToEH() 访问到的就是这个被模拟的版本。
通过采纳这种模块结构和测试策略,开发者可以有效地解决 Jest 模拟函数在模块内部调用中引用丢失的问题,从而编写出更健壮、更可靠的单元测试。
以上就是Jest 测试中模块内函数调用的 Mock 策略:解决引用传递问题的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号