@jsdevtools/chai-exec
Version:
Chai assertions for testing your CLI
347 lines (248 loc) • 12 kB
Markdown
Chai Exec
=======================
#### Chai assertions for testing your CLI
[](https://github.com/JS-DevTools/chai-exec/actions)
[](https://github.com/JS-DevTools/chai-exec/actions)
[](https://coveralls.io/github/JS-DevTools/chai-exec?branch=master)
[](https://david-dm.org/JS-DevTools/chai-exec)
[](https://www.npmjs.com/package/@jsdevtools/chai-exec)
[](LICENSE)
[](https://plant.treeware.earth/JS-DevTools/chai-exec)
Features
--------------------------
- **Easy to use**<br>
Pass your CLI and arguments as a single string, an array of strings, or as separate parameters.
- **Don't repeat yourself**<br>
Set your common [defaults](#chaiexecdefaults) _once_. Each test ony needs to specify the arguments that are unique to it.
- **Fluent assertions**<br>
Test your CLI using intuitive fluent syntax, such as `myCLI.should.exit.with.code(0)` or `myCLI.stdout.should.contain("some string")`.
- **Async Support**<br>
Just use `await chaiExecAsync()` instead of `chaiExec()`. Everything else is the same.
- **Windows Support**<br>
Excellent Windows support, thanks to [cross-spawn](https://github.com/moxystudio/node-cross-spawn).
Related Projects
--------------------------
- [ez-spawn](https://github.com/JS-DevTools/ez-spawn) - Simple, consistent process spawning
Examples
--------------------------
```javascript
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, "");
});
});
```
Installation
--------------------------
Install using [npm](https://docs.npmjs.com/getting-started/what-is-npm):
```bash
npm install /chai-exec
```
Then require it in your test file and register it with Chai:
```javascript
const chaiExec = require("@jsdevtools/chai-exec");
const chai = require("chai");
chai.use(chaiExec);
```
Usage
--------------------------
### `chaiExec(cli, [args], [options])`
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:
```javascript
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](https://github.com/JS-DevTools/ez-spawn#options-object) for details about the `options` parameter.
### `chaiExecAsync(cli, [args], [options])`
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:
```javascript
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.
```javascript
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, "");
});
});
```
### `chaiExec.defaults`
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)<br>
The name or path of your CLI. Set this once, and then you only ever need to pass arguments to `chaiExec()`
- `defaults.args` (string or array of strings)<br>
Arguments to pass to your CLI every time. If you pass additional arguments when you call `chaiExec()`, they will be appended to the default arguments.
- `defaults.options` ([options object](https://github.com/JS-DevTools/ez-spawn#options-object))<br>
Default options to use every time. If you pass additional options when you call `chaiExec()`, they will be merged with the default arguments.
```javascript
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" ]);
});
});
```
Assertions
--------------------------
### `.exitCode(number, [message])`
**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.
```javascript
// 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);
```
### `.stdout(string, [message])`
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.
```javascript
// 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!$/);
```
### `.stderr(string, [message])`
Asserts on your CLI's stderr output (errors and warnings). You can test for a specific string, a substring, or a regular expression.
```javascript
// 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!$/);
```
### `.output(string, [message])`
Asserts on **all** of your CLI's output (stdout + output). You can test for a specific string, a substring, or a regular expression.
```javascript
// 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!$/);
```
Contributing
--------------------------
Contributions, enhancements, and bug-fixes are welcome! [Open an issue](https://github.com/JS-DevTools/chai-exec/issues) on GitHub and [submit a pull request](https://github.com/JS-DevTools/chai-exec/pulls).
#### Building/Testing
To build/test the project locally on your computer:
1. __Clone this repo__<br>
`git clone hhttps://github.com/JS-DevTools/chai-exec.git`
2. __Install dependencies__<br>
`npm install`
3. __Run the tests__<br>
`npm test`
License
--------------------------
Chai Exec is 100% free and open-source, under the [MIT license](LICENSE). Use it however you want.
This package is [Treeware](http://treeware.earth). If you use it in production, then we ask that you [**buy the world a tree**](https://plant.treeware.earth/JS-DevTools/chai-exec) to thank us for our work. By contributing to the Treeware forest you’ll be creating employment for local families and restoring wildlife habitats.
Big Thanks To
--------------------------
Thanks to these awesome companies for their support of Open Source developers ❤
[](https://travis-ci.com)
[](https://saucelabs.com)
[](https://coveralls.io)