An easy-to-use mock for child_process.spawn
.
- 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
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);
});
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
};
});
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 writethis.stderr
- the standard error of the process to which it can writethis.command
- the command for thespawn
callthis.args
- the args for thespawn
callthis.opts
- the options object passed to thespawn
callthis.emit
- the emit method of the underlyingEventEmitter
The process "runs" until the runner calls the callback.
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.
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
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
sets the default processing of all spawn invocations to use the runner function specified
- fn - a runner function to handler default processing
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 tospawn
Do not mix sequence.add
and setStrategy
calls for a specific run.
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.
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.
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 commandargs
- the command argumentsopts
- the options passed to the spawn invocationexitCode
- the exit code of the processsignal
- the signal delivered to the process (simulated via the runner)
BSD. See accompanying LICENSE file.
The following third-party libraries are used by this module:
Pull requests welcome!
child_process.fork
andchild_process.exec
processing- strategy functions on
process.kill