【问题标题】:How to mock the Node.js child_process spawn function?如何模拟 Node.js child_process spawn 函数?
【发布时间】:2015-01-06 12:41:01
【问题描述】:

有没有简单的方法来模拟 Node.js child_process spawn 函数?

我有如下代码,并希望在单元测试中对其进行测试,而不必依赖实际的工具调用:

var output;
var spawn = require('child_process').spawn;
var command = spawn('foo', ['get']);

command.stdout.on('data', function (data) {
    output = data;
});

command.stdout.on('end', function () {
    if (output) {
        callback(null, true);
    }
    else {
        callback(null, false);
    }
});

是否有(经过验证和维护的)库允许我模拟 spawn 调用并让我指定模拟调用的输出?

我不想依赖工具或操作系统来保持测试的简单和孤立。我希望能够运行测试而无需设置复杂的测试装置,这可能意味着大量工作(包括更改系统配置)。

有没有简单的方法可以做到这一点?

【问题讨论】:

    标签: node.js mocking spawn


    【解决方案1】:

    你可以使用 sinon.stubs sinon stubs guide

    // i like the sandbox, or you can use sinon itself
    let sandbox = sinon.sandbox.create();
    
    let spawnEvent = new events.EventEmitter();
    spawnEvent.stdout = new events.EventEmitter();
    
    sandbox.stub(child_process, 'spawn').returns(spawnEvent);
    
    // and emit your event
    spawnEvent.stdout.emit('data', 'hello world');
    
    console.log(output)  // hello world
    

    【讨论】:

      【解决方案2】:

      我找到了mock-spawn 库,它几乎可以满足我的需求。它允许模拟spawn 调用并将预期结果返回给调用测试。

      一个例子:

      var mockSpawn = require('mock-spawn');
      
      var mySpawn = mockSpawn();
      require('child_process').spawn = mySpawn;
      
      mySpawn.setDefault(mySpawn.simple(1 /* exit code */, 'hello world' /* stdout */));
      

      可以在项目页面上找到更多高级示例。

      【讨论】:

      • 我看到你上面的帖子是我现在正在处理的类似请求之一,我使用了 mock-spawn 库并且能够模拟 child_process,但我现在面临的一个问题是在我的一种方法中,我有一个类似“command.stdout.on('data',function(data){})”的“on”监听器,但是当我从测试套件调用该方法时,它不会触发这个事件,我想知道如何在我的单元测试套件中模拟它......
      【解决方案3】:

      遇到这个问题,nwinkler 的回答让我走上了正轨。下面是一个 Mocha、Sinon 和 Typescript 示例,它将 spawn 包装在一个 Promise 中,解析退出代码是否为零,否则拒绝,它收集 STDOUT/STDERR 输出,并允许您通过 STDIN 输入文本。测试失败只是测试异常的问题。

      function spawnAsPromise(cmd: string, args: ReadonlyArray<string> | undefined, options: child_process.SpawnOptions | undefined, input: string | undefined) {
          return new Promise((resolve, reject) => {
              // You could separate STDOUT and STDERR if your heart so desires...
              let output: string = '';  
              const child = child_process.spawn(cmd, args, options);
              child.stdout.on('data', (data) => {
                  output += data;
              });
              child.stderr.on('data', (data) => {
                  output += data;
              });
              child.on('close', (code) => {
                  (code === 0) ? resolve(output) : reject(output);
              });
              child.on('error', (err) => {
                  reject(err.toString());
              });
      
              if(input) {            
                  child.stdin.write(input);
                  child.stdin.end();
              }
          });
      }
      
      // ...
      
      describe("SpawnService", () => {
          it("should run successfully", async() => {
              const sandbox = sinon.createSandbox();
              try {
                  const CMD = 'foo';
                  const ARGS = ['--bar'];
                  const OPTS = { cwd: '/var/fubar' };
      
                  const STDIN_TEXT = 'I typed this!';
                  const STDERR_TEXT = 'Some diag stuff...';
                  const STDOUT_TEXT = 'Some output stuff...';
      
                  const proc = <child_process.ChildProcess> new events.EventEmitter();
                  proc.stdin = new stream.Writable();
                  proc.stdout = <stream.Readable> new events.EventEmitter();
                  proc.stderr = <stream.Readable> new events.EventEmitter();
      
                  // Stub out child process, returning our fake child process
                  sandbox.stub(child_process, 'spawn')
                      .returns(proc)    
                      .calledOnceWith(CMD, ARGS, OPTS);
      
                  // Stub our expectations with any text we are inputing,
                  // you can remove these two lines if not piping in data
                  sandbox.stub(proc.stdin, "write").calledOnceWith(STDIN_TEXT);
                  sandbox.stub(proc.stdin, "end").calledOnce = true;
      
                  // Launch your process here
                  const p = spawnAsPromise(CMD, ARGS, OPTS, STDIN_TEXT);
      
                  // Simulate your program's output
                  proc.stderr.emit('data', STDERR_TEXT);
                  proc.stdout.emit('data', STDOUT_TEXT);
      
                  // Exit your program, 0 = success, !0 = failure
                  proc.emit('close', 0);
      
                  // The close should get rid of the process
                  const results = await p;
                  assert.equal(results, STDERR_TEXT + STDOUT_TEXT);
              } finally {
                  sandbox.restore();
              }
          });
      });
      

      【讨论】:

      • 对于不使用 TypeScript 的任何人,我的 proc 和 stdout/stderr 声明如下:const proc = new child_process.ChildProcess(); proc.stdout = new Readable(); proc.stderr = new Readable(); 其中 Readable 是导入 const Readable = require('stream')
      • Options 参数的类型应该是 child_process.SpawnOptions 而不是 options: child_process.SpawnSyncOptions。同步选项不同,允许传递input,这消除了写入stdin 进行同步调用的必要性。但是,其余代码是正确的,正是我所需要的。非常感谢。
      【解决方案4】:

      对于任何对此特定问题仍有疑问并且由于某种原因,其他答案中的建议无济于事的人,我能够通过替换真正的 child_process 使其与 proxyrequire (https://github.com/thlorenz/proxyquire) 一起使用生成一个事件发射器,然后我在测试中使用它来模拟发射。

      var stdout = new events.EventEmitter();
      var stderr = new events.EventEmitter();
      var spawn = new events.EventEmitter();
      spawn.stderr = stderr;
      spawn.stdout = stdout;
      
      var child_process = {
        spawn: () => spawn,
        stdout,
        stderr
      };
      
      // proxyrequire replaces the child_process require in the file pathToModule
      var moduleToTest = proxyquire("./pathToModule/", {
        'child_process': child_process
      });
      
      describe('Actual test', function () {
        var response;
      
        before(function (done) {
          // your regular method call
          moduleToTest.methodToTest()
          .then(data => {
            response = data;
            done();
          }).catch(err => {
            response = err;
            done();
          });
      
          // emit your expected response
          child_process.stdout.emit("data", "the success message sent");
          // you could easily use the below to test an error
          // child_process.stderr.emit("data", "the error sent");
        });
      
        it('test your expectation', function () {
          expect(response).to.equal("the success message or whatever your moduleToTest 
            resolves with");
        });
      });
      
      

      希望这会有所帮助...

      【讨论】:

        猜你喜欢
        • 2021-10-31
        • 2023-01-25
        • 1970-01-01
        • 2016-09-21
        • 2016-08-13
        • 2021-04-05
        • 2016-09-01
        • 2012-07-27
        • 2021-11-27
        相关资源
        最近更新 更多