Overview

I think you all know the popular library for creating CLIs called yargs. That is what we are going to be using. Our CLI should reverse a string.

$ reverse reverse --string string
gnirts

Setup

Create a folder for your project. Then run these commands inside of it.

$ npm init -y
$ npm install -D typescript @types/yargs @types/node
$ npm install --save yargs

Make sure you set the bin attribute of your package.json to dist/cli.js and the main to dist/index.js. Make your tsconfig.json look like this:

{
    "compilerOptions": {
      "esModuleInterop": true,
      "module": "CommonJS",
      "moduleResolution": "node",
      "outDir": "./dist",
      "target": "ESNext"
    },
    "exclude": ["node_modules", "**/*.spec.ts"],
    "include": ["src/**/*"]
  }

Creating The CLI

Inside src/cli.ts, write this:

#!/usr/bin/env node
import yargs from "yargs";

yargs
  .scriptName("reverse")
  .usage("$0 <cmd> [args]")
  .command(
    "reverse [string]",
    "reverse the string",
    (y) => {
      y.positional("string", {
        type: "string",
        default: "string",
        describe: "string to reverse",
      });
    },
    (argv) => {
      console.log(argv.string.split("").reverse().join(""));
    }
  )
  .help().argv;

and now you have a working CLI!

Unit Testing API

First before we create actual tests, we need to change the structure of the project. Create a file called src/index.ts and put this inside of it:

export function reverseString(str: string) {
    return str.split("").reverse().join("");
}

Inside of src/cli.ts add an import statement to the top to import reverseString from index.ts and change the callback to do this:

console.log(reverseString((argv.string as string)));

So now our CLI has the structure to support unit testing! So now run these commands:

$ npm install -D mocha chai

Also, set your test script to tsc && mocha test/**/*.js. Now under test/api/reverseString.spec.js write this:

const { expect } = require("chai");
const { reverseString } = require("../../dist/index");

describe(".reverseString", () => {
  it("should reverse properly", () => {
    expect(reverseString("foo")).to.equal("oof");
  });
});

But, this really isn't testing the actual CLI, just the API under it.

Testing the CLI

Under test/cli/reverse.spec.js write this:

const { expect } = require("chai");
const { execSync } = require("child_process");

const test = (args) => {
  return execSync(`node dist/cli.js reverse ${args}`).toString();
};

describe("CLI", () => {
  it("should use the positional argument", () => {
    expect(test("--string foo")).to.equal("oof\n");
  });
  it("should use the non positional argument", () => {
    expect(test("foo")).to.equal("oof\n");
  });
});

This is probably the worst way to test it, so if you have a better way, feel free to put it in the comments.

Back | DEV.to

shadowtime2000

If you are looking at this you probably wonder who I am; teenage open source maintainer

shadowtime2000's DEV profile image