How to mock the Node.js child_process spawn function?
For anyone who still has problems with this particular problem and for some reason, the recommendations in other answers don't help, I was able to get it to work with proxyrequire
(https://github.com/thlorenz/proxyquire) by replacing the real child_process spawn with an event emitter that I then used in my tests to mock the emission.
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");
});
});
Hope this helps...
you can use 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
I've found the mock-spawn library, which pretty much does what I want. It allows to mock the spawn
call and provide expected results back to the calling test.
An example:
var mockSpawn = require('mock-spawn');
var mySpawn = mockSpawn();
require('child_process').spawn = mySpawn;
mySpawn.setDefault(mySpawn.simple(1 /* exit code */, 'hello world' /* stdout */));
More advanced examples can be found on the project page.
Came across this and nwinkler's answer put me on the path. Below is a Mocha, Sinon and Typescript example that wraps the spawn in a promise, resolving if the exit code is a zero, and rejecting otherwise, It gathers up STDOUT/STDERR output, and lets you pipe text in through STDIN. Testing for a failure would be just a matter of testing for the exception.
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();
}
});
});