UNPKG

@jsdevtools/chai-exec

Version:

Chai assertions for testing your CLI

347 lines (248 loc) 12 kB
Chai Exec ======================= #### Chai assertions for testing your CLI [![Cross-Platform Compatibility](https://jstools.dev/img/badges/os-badges.svg)](https://github.com/JS-DevTools/chai-exec/actions) [![Build Status](https://github.com/JS-DevTools/chai-exec/workflows/CI-CD/badge.svg)](https://github.com/JS-DevTools/chai-exec/actions) [![Coverage Status](https://coveralls.io/repos/github/JS-DevTools/chai-exec/badge.svg?branch=master)](https://coveralls.io/github/JS-DevTools/chai-exec?branch=master) [![Dependencies](https://david-dm.org/JS-DevTools/chai-exec.svg)](https://david-dm.org/JS-DevTools/chai-exec) [![npm](https://img.shields.io/npm/v/@jsdevtools/chai-exec.svg)](https://www.npmjs.com/package/@jsdevtools/chai-exec) [![License](https://img.shields.io/npm/l/@jsdevtools/chai-exec.svg)](LICENSE) [![Buy us a tree](https://img.shields.io/badge/Treeware-%F0%9F%8C%B3-lightgreen)](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 @jsdevtools/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 [![Travis CI](https://jstools.dev/img/badges/travis-ci.svg)](https://travis-ci.com) [![SauceLabs](https://jstools.dev/img/badges/sauce-labs.svg)](https://saucelabs.com) [![Coveralls](https://jstools.dev/img/badges/coveralls.svg)](https://coveralls.io)