-
Easy to use
Pass your CLI and arguments as a single string, an array of strings, or as separate parameters. -
Don't repeat yourself
Set your common defaults once. Each test ony needs to specify the arguments that are unique to it. -
Fluent assertions
Test your CLI using intuitive fluent syntax, such asmyCLI.should.exit.with.code(0)
ormyCLI.stdout.should.contain("some string")
. -
Async Support
Just useawait chaiExecAsync()
instead ofchaiExec()
. Everything else is the same. -
Windows Support
Excellent Windows support, thanks to cross-spawn.
- ez-spawn - Simple, consistent process spawning
const chaiExec = require("@jsdevtools/chai-exec");
const chai = require("chai");
chai.use(chaiExec);
describe("My CLI", () => {
it("should exit with a zero exit code", () => {
// Run your CLI
let myCLI = chaiExec('my-cli --arg1 --arg2 "some other arg"');
// Should syntax
myCLI.should.exit.with.code(0);
myCLI.stdout.should.contain("Success!");
myCLI.stderr.should.be.empty;
// Expect sytnax
expect(myCLI).to.exit.with.code(0);
expect(myCLI).stdout.to.contain("Success!");
expect(myCLI).stderr.to.be.empty;
// Assert syntax
assert.exitCode(myCLI, 0);
assert.stdout(myCLI, "Success!");
assert.stderr(myCLI, "");
});
});
Install using npm:
npm install @jsdevtools/chai-exec
Then require it in your test file and register it with Chai:
const chaiExec = require("@jsdevtools/chai-exec");
const chai = require("chai");
chai.use(chaiExec);
You can pass your CLI and its arguments as a single string, an array of strings, or as separate parameters. The following examples all do the same thing:
chaiExec(`git commit -am "Fixed a bug"`); // Pass the CLI and args as a single string
chaiExec("git", "commit", "-am", "Fixed a bug"); // Pass the CLI and args as separate params
chaiExec(["git", "commit", "-am", "Fixed a bug"]); // Pass the CLI and args as an array
chaiExec("git", ["commit", "-am", "Fixed a bug"]); // Pass the CLI as a string and args as an array
See ez-spawn options for details about the options
parameter.
The chaiExecAsync()
function works exactly the same as chaiExec()
, except that it runs your CLI asynchronously and returns a Promise
that resolves when the CLI exits. You'll need to explicitly require the chaiExecAsync
export, like this:
const { chaiExecAsync } = require("@jsdevtools/chai-exec");
You can then use chaiExecAsync
exactly the same as chaiExec
, but remember to use the async
and await
keywords, since it's asynchronous.
const { chaiExecAsync } = require("@jsdevtools/chai-exec");
const chai = require("chai");
chai.use(chaiExecAsync);
describe("My CLI", () => {
it("should exit with a zero exit code", async () => {
// Run your CLI
let myCLI = await chaiExecAsync('my-cli --arg1 --arg2 "some other arg"');
// Should syntax
myCLI.should.exit.with.code(0);
myCLI.stdout.should.contain("Success!");
myCLI.stderr.should.be.empty;
// Expect sytnax
expect(myCLI).to.exit.with.code(0);
expect(myCLI).stdout.to.contain("Success!");
expect(myCLI).stderr.to.be.empty;
// Assert syntax
assert.exitCode(myCLI, 0);
assert.stdout(myCLI, "Success!");
assert.stderr(myCLI, "");
});
});
When writing tests for a CLI, you'll often want to use the same command, args, and/or options for every test. Rather than repeating the same parameters every time you call chaiExec
, you can just set chaiExec.defaults
once. Your default values will be used for every subsequent chaiExec()
call. You can specify additional CLI arguments and/or options for each call, in addition to the defaults.
-
defaults.command
(string)
The name or path of your CLI. Set this once, and then you only ever need to pass arguments tochaiExec()
-
defaults.args
(string or array of strings)
Arguments to pass to your CLI every time. If you pass additional arguments when you callchaiExec()
, they will be appended to the default arguments. -
defaults.options
(options object)
Default options to use every time. If you pass additional options when you callchaiExec()
, they will be merged with the default arguments.
const chaiExec = require("@jsdevtools/chai-exec");
const chai = require("chai");
chai.use(chaiExec);
// Set some defaults
chaiExec.defaults = {
command: "my-cli",
args: "--arg1 --arg2",
options: {
cwd: "/usr/bin"
}
};
describe("My CLI", () => {
it("should use defaults", () => {
// Run your CLI using defaults + one-time args
let myCLI("--arg3 --arg4");
myCLI.command.should.equal("my-cli");
myCLI.args.should.deep.equal([ "--arg1", "--arg2", "--arg3", "--arg4" ]);
});
});
aliases: .exit.code
or .status
Asserts on your CLI's exit code. You can test for a specific code, a list of codes, or a range.
// Should syntax
myCLI.exitCode.should.equal(0);
myCLI.should.have.exitCode(0);
myCLI.should.exit.with.code(0);
myCLI.should.exit.with.a.code.that.is.oneOf(0, [0, 1, 2, 3]);
myCLI.should.have.an.exit.code.of.at.least(0).and.at.most(5);
// Expect sytnax
expect(myCLI).exitCode.to.equal(0);
expect(myCLI).to.have.exitCode(0);
expect(myCLI).to.exit.with.code(0);
expect(myCLI).to.exit.with.a.code.that.is.oneOf([0, 1, 2, 3]);
expect(myCLI).to.have.an.exit.code.of.at.least(0).and.at.most(5);
// Assert syntax
assert.equal(myCLI.exitCode, 0);
assert.exitCode(myCLI, 0);
assert.exitCode(myCLI, [0, 1, 2, 3]);
assert.notExitCode(myCLI, 1);
assert.notExitCode(myCLI, [1, 2, 3]);
assert.exitCodeBetween(myCLI, 0, 5);
assert.exitCodeNotBetween(myCLI, 1, 5);
Asserts on your CLI's standard output (non-error, non-warning output). You can test for a specific string, a substring, or a regular expression.
// Should syntax
myCLI.stdout.should.equal("Success!");
myCLI.should.have.stdout.that.contains("Success!");
myCLI.should.have.stdout.that.does.not.contain("Failure!");
myCLI.should.have.stdout.that.matches(/^Success!$/);
myCLI.should.have.stdout.that.does.not.match(/^Failure!$/);
// Expect syntax
expect(myCLI).stdout.to.equal("Success!");
expect(myCLI).to.have.stdout.that.contains("Success!");
expect(myCLI).to.have.stdout.that.does.not.contain("Failure!");
expect(myCLI).to.have.stdout.that.matches(/^Success!$/);
expect(myCLI).to.have.stdout.that.does.not.match(/^Failure!$/);
// Assert syntax
assert.stdout(myCLI, "Success!");
assert.stdout(myCLI, /^Success!$/);
assert.include(myCLI.stdout, "Success!");
assert.notInclude(myCLI.stdout, "Failure!");
assert.match(myCLI.stdout, /^Success!$/);
assert.notMatch(myCLI.stdout, /^Failure!$/);
Asserts on your CLI's stderr output (errors and warnings). You can test for a specific string, a substring, or a regular expression.
// Should syntax
myCLI.stderr.should.equal("Failure!");
myCLI.should.have.stderr.that.contains("Failure!");
myCLI.should.have.stderr.that.does.not.contain("Success!");
myCLI.should.have.stderr.that.matches(/^Failure!$/);
myCLI.should.have.stderr.that.does.not.match(/^Success!$/);
// Expect syntax
expect(myCLI).stderr.to.equal("Failure!");
expect(myCLI).to.have.stderr.that.contains("Failure!");
expect(myCLI).to.have.stderr.that.does.not.contain("Success!");
expect(myCLI).to.have.stderr.that.matches(/^Failure!$/);
expect(myCLI).to.have.stderr.that.does.not.match(/^Success!$/);
// Assert syntax
assert.stderr(myCLI, "Failure!");
assert.stderr(myCLI, /^Failure!$/);
assert.include(myCLI.stderr, "Failure!");
assert.notInclude(myCLI.stderr, "Success!");
assert.match(myCLI.stderr, /^Failure!$/);
assert.notMatch(myCLI.stderr, /^Success!$/);
Asserts on all of your CLI's output (stdout + output). You can test for a specific string, a substring, or a regular expression.
// Should syntax
myCLI.output.should.equal("Success!");
myCLI.should.have.output.that.contains("Failure!");
myCLI.should.have.output.that.does.not.contain("Success!");
myCLI.should.have.output.that.matches(/^(Success|Failure)!$/);
myCLI.should.have.output.that.does.not.match(/^(Success|Failure)!$/);
// Expect syntax
expect(myCLI).output.to.equal("Success!");
expect(myCLI).to.have.output.that.contains("Failure!");
expect(myCLI).to.have.output.that.does.not.contain("Success!");
expect(myCLI).to.have.output.that.matches(/^(Success|Failure)!$/);
expect(myCLI).to.have.output.that.does.not.match(/^(Success|Failure)!$/);
// Assert syntax
assert.output(myCLI, "Failure!");
assert.output(myCLI, /^(Success|Failure)!$/);
assert.include(myCLI.output, "Failure!");
assert.notInclude(myCLI.output, "Success!");
assert.match(myCLI.output, /^Failure!$/);
assert.notMatch(myCLI.output, /^Success!$/);
Contributions, enhancements, and bug-fixes are welcome! File an issue on GitHub and submit a pull request.
To build/test the project locally on your computer:
-
Clone this repo
git clone hhttps://github.com/JS-DevTools/chai-exec.git
-
Install dependencies
npm install
-
Run the tests
npm test
Chai Exec is 100% free and open-source, under the MIT license. Use it however you want.
Thanks to these awesome companies for their support of Open Source developers ❤