You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Jest Mock child_process包的exec方法进行单元测试?

你的Mock基础实现正确性与优化建议

你的基础Mock实现是完全正确的——它确实能把child_process的exec方法替换成Jest的Mock函数,让你在测试中可以控制exec的行为、验证调用情况。不过从测试的灵活性和覆盖场景的角度,还有不少优化空间:

1. 给Mock添加默认的回调行为,避免测试阻塞

原生exec是异步回调风格的,你当前的jest.fn()是一个空Mock,如果被测代码里依赖exec的回调逻辑(比如等待执行结果再继续),测试可能会卡住或者抛出未处理的回调错误。建议给exec添加一个默认的模拟实现:

// __mocks__/child_process.js
const child_process = jest.genMockFromModule('child_process');

// 默认模拟exec执行成功的场景
child_process.exec = jest.fn((command, options, callback) => {
  // 处理参数兼容:如果调用时没传options,callback是第二个参数
  if (typeof options === 'function') {
    callback = options;
  }
  // 触发成功回调
  callback(null, { stdout: 'Mocked exec output', stderr: '' });
});

module.exports = child_process;

这样测试时如果需要模拟失败场景,只需要在具体测试用例里覆盖这个实现即可:

// 在测试用例中模拟exec执行失败
child_process.exec.mockImplementation((cmd, cb) => {
  cb(new Error('Mocked connection failure'));
});

2. 支持Promise风格的调用(如果你的代码用了util.promisify)

很多现代Node.js代码会用util.promisify(exec)把回调风格的exec转成Promise,这时你的Mock需要同时支持两种调用方式:

// __mocks__/child_process.js
const child_process = jest.genMockFromModule('child_process');
const mockExec = jest.fn();

child_process.exec = mockExec;

// 同时兼容回调和Promise调用
mockExec.mockImplementation((command, options, callback) => {
  // 处理参数兼容
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }

  // 处理回调调用
  if (callback) {
    callback(null, { stdout: 'Mocked success', stderr: '' });
  }

  // 处理Promise调用(promisify会返回这个Promise)
  return Promise.resolve({ stdout: 'Mocked success', stderr: '' });
});

module.exports = child_process;

3. 测试用例中的最佳实践

在测试runCassandraMigration时,记得不仅要模拟exec的行为,还要验证它是否被正确调用:

test('successfully runs Cassandra migration', async () => {
  // 模拟exec返回成功结果
  child_process.exec.mockImplementation((cmd, cb) => {
    cb(null, { stdout: 'Migration completed successfully' });
  });

  // 执行被测函数
  await runCassandraMigration(defaultArguments());

  // 验证exec被调用了正确的命令(比如包含你的迁移脚本路径)
  expect(child_process.exec).toHaveBeenCalledWith(
    expect.stringContaining('./home'), // 匹配你的migration_script_path
    expect.any(Object), // 匹配options参数(如果有的话)
    expect.any(Function) // 匹配回调函数
  );
});

test('handles migration failure gracefully', async () => {
  const mockError = new Error('Cassandra connection timed out');
  child_process.exec.mockImplementation((cmd, cb) => {
    cb(mockError);
  });

  // 验证被测函数会抛出对应的错误
  await expect(runCassandraMigration(defaultArguments())).rejects.toThrow(mockError);
});

4. 可选:直接在测试文件中Mock(适合单文件专用Mock)

如果你不需要在多个测试文件复用这个Mock,也可以直接在测试文件开头编写Mock,无需__mocks__文件夹:

// 在测试文件顶部
jest.mock('child_process', () => {
  // 保留其他原生方法,只Mock exec
  const original = jest.requireActual('child_process');
  return {
    ...original,
    exec: jest.fn((cmd, cb) => cb(null, { stdout: 'Mocked output' }))
  };
});

内容的提问来源于stack exchange,提问作者David Faizulaev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 04:15:45