如何使用Jest模拟测试Node.js CLI?

7
我卡在了最开始的阶段,只需要CLI并捕获其输出。我尝试了两种方法,但都不起作用。
这是我的cli.js:
#!/usr/bin/env node

console.log('Testing...');
process.exit(0);

这是我的 cli.test.js 文件:
test('Attempt 1', () => {
    let stdout = require("test-console").stdout;
    let output = stdout.inspectSync(function() {
        require('./cli.js');
    });
    expect(output).toBe('Testing...');
});

test('Attempt 2', () => {
    console.log = jest.fn();
    require('./cli.js');
    expect(console.log.calls).toBe(['Testing...']);
});

无论运行哪个测试,输出结果都是相同的:“不太重要”。
$ jest

 RUNS  bin/cli.test.js
Done in 3.10s.

1
我想这是因为 process.exit。尝试进行存根处理。 - Estus Flask
你说得完全正确,谢谢!这也是我在实际项目中使用 commander 时遇到的问题。就是这一行:https://github.com/tj/commander.js/blob/dcddf698c5463795401ad3d6382f5ec5ec060478/index.js#L1157 - AndyO
当然可以...我想我可以简单地使用outputHelp()而不是help()。 - AndyO
1个回答

11

除了对环境的依赖外,Node.js CLI 应用程序与其他应用程序没有什么不同。它们预计要广泛使用 process 成员,例如:

  • process.stdin
  • process.stdout
  • process.argv
  • process.exit

如果使用了以上任何一项内容,则应相应地进行模拟和测试。

由于直接调用 console.log 进行输出,因此可以直接对其进行监视,尽管也可以使用类似 test-console 的辅助包。

在这种情况下,在导入的文件中调用了 process.exit(0),因此规范文件会提前退出,下一个 Done 输出来自父进程。它应该被存根化。抛出错误是必要的,以停止代码执行-以模仿正常行为:

test('Attempt 2', () => {
    const spy = jest.spyOn(console, 'log');
    jest.spyOn(process, 'exit').mockImplementationOnce(() => {
      throw new Error('process.exit() was called.')
    });

    expect(() => {
      require('./cli.js');
    }).toThrow('process.exit() was called.');
    expect(spy.mock.calls).toEqual([['Testing...']]);
    expect(process.exit).toHaveBeenCalledWith(0);
});

您介意为拦截 process.stdout 添加一个示例吗?因为 test-console 的返回值无法使用(可能是因为 jest 钩入了 stdout 并将颜色和元数据添加到输出中?),而在测试中使用 process.stdout.on('data', ...) 会导致“读取 ENOTCONN”错误。 - AndyO
我没有用Jest做过这个,所以对其中的陷阱不太了解。我猜这是一个已知的问题,https://github.com/facebook/jest/issues/1120,并且在控制台方法上进行间谍操作是正确的做法。看起来Jest会干扰std*流。我猜一个解决办法是使用child_process运行被测试的脚本,并从其stdout中读取(可能会很麻烦,因为你需要编写包装脚本来提供原始脚本的模拟)。看起来这是一个复杂的问题,如果是你的情况,值得提出一个单独的问题。 - Estus Flask
啊,我明白了 - 不知道Jest在详细模式下的输出是什么样子的。控制台的问题在于一些包可能会直接写入stdout:https://github.com/tj/commander.js/blob/dcddf698c5463795401ad3d6382f5ec5ec060478/index.js#L1145 这让我想知道这是否是良好的代码设计,或者应该报告一下。无论如何 - 感谢您提供的链接! - AndyO
有道理。我猜你可能想要模拟Command.prototype.outputHelp。 - Estus Flask
1
在规范中,使用try...catch来抑制错误并不是一个好的做法。可能会有其他错误导致没有反馈。由于存在特定的错误,应该使用toThrow进行断言。 - Estus Flask

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接