Test command line on Node.js
Coffee
Test command line on Node.js.
Install
$ npm i coffee --save-dev
Usage
Coffee is useful for test command line in test frammework (like Mocha).
Fork
You can use fork for spawning Node processes.
const coffee = require('coffee');
describe('cli', () => { it('should fork node cli', () => { return coffee.fork('/path/to/file.js') .expect('stdout', '12\n') .expect('stderr', /34/) .expect('code', 0) .end(); }); });
In file.js
console.log(12);
console.error(34);
You can pass args and opts to childprocess fork.
coffee.fork('/path/to/file.js', [ 'args' ], { execArgv: [ '--inspect' ]})
.expect('stdout', '12\n')
.expect('stderr', '34\n')
.expect('code', 0)
.end();
And more:
coffee.fork('/path/to/file.js')
// print origin stdio
.debug()
// inject a script .beforeScript(mockScript)
// interact with prompt .waitForPrompt() .write('tz\n')
// string strict equals .expect('stdout', 'abcdefg') // regex .expect('stdout', /^abc/) // multiple .expect('stdout', [ 'abcdefg', /abc/ ]) .expect('code', 0) .end();
see the API chapter below for more details.
Spawn
You can also use spawn for spawning normal shell scripts.
coffee.spawn('cat')
.write('1')
.write('2')
.expect('stdout', '12')
.expect('code', 0)
.end();
Rule
code
Check the exit code.
coffee.fork('/path/to/file.js', [ 'args' ])
.expect('code', 0)
// .expect('code', 1)
.end();
stdout / stderr
Check the stdout and stderr.
coffee.fork('/path/to/file.js', [ 'args' ])
.expect('stdout', '12\n')
.expect('stderr', '34\n')
.expect('code', 0)
.end();
custom
Support custom rules, see test/fixtures/extendable for more details.
const { Coffee, Rule } = require('coffee');
class FileRule extends Rule { constructor(opts) { super(opts); // args is which pass to expect(type, ...args), expected is the first args. const { args, expected } = opts; }
assert(actual, expected, message) { // do sth return super.assert(fs.existsSync(expected), true, should exists file ${expected}); } }
class MyCoffee extends Coffee { constructor(...args) { super(...args); this.setRule('file', FileRule); }
static fork(modulePath, args, opt) { return new MyCoffee({ method: 'fork', cmd: modulePath, args, opt, }); } }
Usage:
// test/custom.test.js
const coffee = require('MyCoffee');
coffee.fork('/path/to/file.js', [ 'args' ])
.expect('file', ${root}/README.md);
.notExpect('file', ${root}/not-exist);
Support multiple process coverage with nyc
Recommend to use [nyc] for coverage, you can use any test frammework supported by nyc.
API
coffee.spawn
Run command using child_process.spawn, then return Coffee instance.
Arguments see childprocess.spawn
coffee.fork
Run command using child_process.fork, then return Coffee instance.
Arguments see childprocess.fork
coffee.Coffee
Assertion object
coffee.expect(type, ...args)
Assert type with expected value, expected value can be string, regular expression, and array.
coffee.spawn('echo', [ 'abcdefg' ])
.expect('stdout', 'abcdefg')
.expect('stdout', /^abc/)
.expect('stdout', [ 'abcdefg', /abc/ ])
.end();
Accept type: stdout / stderr / code / error, see built-in rules description above.
coffee.notExpect(type, ...args)
The opposite assertion of expect.
coffee.includes(type, ...args)
Assert type with expected string value, expected value should be string only.
coffee.spawn('echo', [ 'abcdefg' ])
.includes('stdout', 'abc')
.expect('stdout', [ 'abc', 'efg' ])
.end();
Accept type: stdout / stderr, see built-in rules description above.
coffee.notIncludes(type, ...args)
The opposite assertion of includes.
coffee.write(data)
Write data to stdin.
coffee.fork(path.join(fixtures, 'stdin.js'))
.write('1\n')
.write('2')
.expect('stdout', '1\n2')
.end();
coffee.writeKey(...args)
Write special key sequence to stdin, support UP / DOWN / LEFT / RIGHT / ENTER / SPACE.
All args will join as one key.
coffee.fork(path.join(fixtures, 'stdin.js'))
.writeKey('1', 'ENTER', '2')
.expect('stdout', '1\n2')
.end();
coffee.waitForPrompt(bool)
If you set false, coffee will write stdin immediately, otherwise will wait for prompt message.
coffee.fork('/path/to/cli', [ 'abcdefg' ])
.waitForPrompt()
.write('tz\n')
// choose the second item
.writeKey('DOWN', 'DOWN', 'ENTER');
.end(done);
cli process should emit prompt message:
Or use coffee.on('stdout', callback) instead, see docs below.
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
function ask(q, callback) { process.send({ type: 'prompt' }); rl.question(q, callback); }
ask('What\'s your name? ', answer => { console.log(hi, ${answer}); ask('How many coffee do you want? ', answer => { console.log(here is your ${answer} coffee); rl.close(); }); });
coffee.end([callback])
Callback will be called after completing the assertion, the first argument is Error if throw exception.
coffee.fork('path/to/cli')
.expect('stdout', 'abcdefg')
.end(done);
// recommended to left undefind and use promise style. const { stdout, stderr, code } = await coffee.fork('path/to/cli').end(); assert(stdout.includes(abcdefg));
coffee.on(event, callback)
Emit stdout/stderr event.
use for kill long-run process:
coffee.fork('path/to/cli')
.on('stdout', (buf, { proc }) => {
if (buf.includes('egg-ready')) {
proc.exitCode = 0;
proc.kill();
}
})
.expect('stdout', 'egg-ready')
.end(done);
use for prompt:
// do not call waitForPrompt / write / writeKey
coffee.fork('path/to/cli')
.on('stdout', (buf, { proc }) => {
if (buf.includes('Your Name: ')) {
proc.stdin.write('TZ\n');
}
})
.expect('stdout', 'Your Name: TZ\n')
.end(done);
coffee.debug(level)
Write data to process.stdout and process.stderr for debug
level can be
- 0 (default): pipe stdout + stderr
- 1: pipe stdout
- 2: pipe stderr
- false: disable
COFFEE_DEBUG env.
coffee.coverage()
If you set false, coffee will not generate coverage.json, default: true.
coffee.beforeScript(scriptFile)
Add a hook script before fork child process run.
coffee.Rule
Assertion Rule base class.
LICENSE
Copyright (c) 2017 - 2019 node-modules. Licensed under the MIT license.
[nyc]: https://github.com/istanbuljs/nyc