Skip to content

Commit

Permalink
Add mock tests to cover subcommand error handling (#1474)
Browse files Browse the repository at this point in the history
  • Loading branch information
shadowspawn authored Feb 17, 2021
1 parent c119028 commit 948796d
Showing 1 changed file with 64 additions and 0 deletions.
64 changes: 64 additions & 0 deletions tests/command.executableSubcommand.mock.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const commander = require('../');
const childProcess = require('child_process');
const EventEmitter = require('events');

// Using mock to allow try/catch around what is otherwise out-of-stack error handling.
// Injecting errors, these are not end-to-end tests.

function makeSystemError(code) {
// We can not make an actual SystemError, but our usage is lightweight so easy to match.
const err = new Error();
err.code = code;
return err;
}

test('when subcommand executable missing (ENOENT) then throw custom message', () => {
// If the command is not found, we show a custom error with an explanation and offer
// some advice for possible fixes.
const mockProcess = new EventEmitter();
const spawnSpy = jest.spyOn(childProcess, 'spawn').mockImplementation(() => { return mockProcess; });
const program = new commander.Command();
program.exitOverride();
program.command('executable', 'executable description');
program.parse(['executable'], { from: 'user' });
expect(() => {
mockProcess.emit('error', makeSystemError('ENOENT'));
}).toThrow('use the executableFile option to supply a custom name'); // part of custom message
spawnSpy.mockRestore();
});

test('when subcommand executable not executable (EACCES) then throw custom message', () => {
// Side note: this error does not actually happen on Windows! But we can still simulate the behaviour on other platforms.
const mockProcess = new EventEmitter();
const spawnSpy = jest.spyOn(childProcess, 'spawn').mockImplementation(() => { return mockProcess; });
const program = new commander.Command();
program.exitOverride();
program.command('executable', 'executable description');
program.parse(['executable'], { from: 'user' });
expect(() => {
mockProcess.emit('error', makeSystemError('EACCES'));
}).toThrow('not executable'); // part of custom message
spawnSpy.mockRestore();
});

test('when subcommand executable fails with other error then return in custom wrapper', () => {
// The existing behaviour is to just silently fail for unexpected errors, as it is happening
// asynchronously in spawned process and client can not catch errors.
const mockProcess = new EventEmitter();
const spawnSpy = jest.spyOn(childProcess, 'spawn').mockImplementation(() => { return mockProcess; });
const program = new commander.Command();
program.exitOverride((err) => {
throw err;
});
program.command('executable', 'executable description');
program.parse(['executable'], { from: 'user' });
let caughtErr;
try {
mockProcess.emit('error', makeSystemError('OTHER'));
} catch (err) {
caughtErr = err;
}
expect(caughtErr.code).toEqual('commander.executeSubCommandAsync');
expect(caughtErr.nestedError.code).toEqual('OTHER');
spawnSpy.mockRestore();
});

0 comments on commit 948796d

Please sign in to comment.