Skip to content

gotwarlost/mock-spawn

Repository files navigation

mock-spawn

Build Status

An easy-to-use mock for child_process.spawn.

Key ideas

  • All mock processes wrap a user-supplied, asynchronous "runner function" that calls a callback to indicate that the process is "done running"
  • You can plug in a "strategy function" that returns runner functions for handling specific spawn invocations.
  • When no specific runner function is found, a default function is used. The baked-in default returns an exit code of 0 and returns immediately. The default function may be overridden for fancier behavior.
  • All the information on how many times spawn was invoked and the details of every invocation are available as attributes on the mock for later assertions
  • Testing simple cases requires no setup whatsoever
  • A simple "sequence" strategy for being able to say: do this on the first invocation, do the other thing on the second invocation and so on is available for use

Usage

Common cases

var mockSpawn = require('mock-spawn');

// override child_process.spawn
// this is a simplistic example; you can use a library like `mockery` to
// set a new instance for every test. See examples/complete/test.js
var mySpawn = mockSpawn();
require('child_process').spawn = mySpawn;

// at this point you have mocked child_process.spawn to always exit 0
// and write nothing to stdout or stderr

// let's change the default processing to exit 1 always and write something
// to stdout
mySpawn.setDefault(mySpawn.simple(1 /* exit code */, 'hello world' /* stdout */));

// let's tell the mock to do specific things on sequential calls
// in this case we exit 0 on the first call, 1 on the second call and so on
mySpawn.sequence.add(mySpawn.simple(0));
mySpawn.sequence.add(mySpawn.simple(1));
mySpawn.sequence.add(function (cb) {
    setTimeout(function () { return cb(2); }, 2000);
});
mySpawn.sequence.add(function (cb) {
    // test the error handling of your library
    this.emit('error', new Error('spawn ENOENT'));
    setTimeout(function() { return cb(8); }, 10);
});
mySpawn.sequence.add({throws:new Error('spawn ENOENT')});

// the fourth call to spawn will use the default function we set up to exit 1

// the fifth call to spawn will emit an error and emit exit with code 8 on the
// next tick of the event loop

// the sixth call to spawn will throw an error synchronously

// call your test library here that invokes spawn the way you expect it to
lib.doSomething(function (err) {
    /* after the test is done running, you can make assertions like so */
    assert.equal(6, mySpawn.calls.length);
    var firstCall = mySpawn.calls[0];
    assert.equal('ls', firstCall.command);
    assert.deepEqual([ '-l' ], firstCall.args);
    assert.equal(0, firstCall.exitCode);
});

Getting fancy with custom strategies

var mockSpawn = require('mock-spawn');

// basic stuff
var mySpawn = mockSpawn();
require('child_process').spawn = mySpawn;

// we are now testing if our library under test retries spawn commands on error
// when executing the `foo` command

var count = 0;
mySpawn.setStrategy(function (command, args, opts) {
    if (command !== 'foo') { return null; } // use default
    if (++count < 3) {
        return mySpawn.simple(1); //exit 1 immediately
    }
    return function (cb) {
        this.stdout.write('output data my library expects');
        return cb(0); // and exit 0
    };
});

API

The runner function

The runner function accepts a single callback that needs to be called with an exit code and optionally a signal name. If you define a throws property on the runner object, it will throw that error synchronously to mimic the behavior of child_process.spawn. It will ignore everything else in this case, and it will not "run" at all. CAVEAT: The throws value must be an instanceof Error.

The runner function has access to the following attributes via this

  • this.stdout - the standard output of the process to which it can write
  • this.stderr - the standard error of the process to which it can write
  • this.command - the command for the spawn call
  • this.args - the args for the spawn call
  • this.opts - the options object passed to the spawn call
  • this.emit - the emit method of the underlying EventEmitter

The process "runs" until the runner calls the callback.

The strategy function

The strategy function accepts 3 arguments: the command, args and options passed to the spawn invocation. It can inspect these to return a customized runner for just that invocation.

It can also return a falsy value to indicate that the default function should be used.

var mySpawn = require('mock-spawn')([verbose])

returns a function that can be plugged into child_process as a replacement for spawn

  • verbose - true to see additional debug messages from this library

var fn = mySpawn.simple(exit-code, [output-data], [error-data])

returns a runner function that exits with the specified code and writes specific data to the output and error streams

Arguments are:

  • exit-code: exit code for the process
  • output-data: the data to be written to standard output
  • error-data: the data to be written to standard error

mySpawn.setDefault(fn)

sets the default processing of all spawn invocations to use the runner function specified

  • fn - a runner function to handler default processing

mySpawn.sequence.add(fn)

enables the sequence strategy and calls the runner function supplied at the specific point in the sequence.

  • fn - the runner function to use. The nth call to add plugs a runner function for the nth invocation to spawn

Do not mix sequence.add and setStrategy calls for a specific run.

mySpawn.setStrategy(fn)

sets fn as the strategy that will return runner functions on demand.

  • fn - the function to be used as the strategy function.

Do not mix sequence.add and setStrategy calls for a specific run.

mySpawn.setSignals(obj)

sets obj as a lookup table for whether to exit. If the value is true, then the runner will emit exit with code null and signal <signal>.

  • obj - the object with signal names and whether to exit.

mySpawn.calls

array of mock process objects that you can use to inspect how your library under test invoked spawn. Every object has the following properties available

  • command - the command
  • args - the command arguments
  • opts - the options passed to the spawn invocation
  • exitCode - the exit code of the process
  • signal - the signal delivered to the process (simulated via the runner)

License

BSD. See accompanying LICENSE file.

Third-party libraries

The following third-party libraries are used by this module:

TODO

Pull requests welcome!

  • child_process.fork and child_process.exec processing
  • strategy functions on process.kill

About

Easy to use mock for child_process.spawn

Resources

License

Stars

Watchers

Forks

Packages

No packages published