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


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";

  .usage("$0 <cmd> [args]")
    "reverse [string]",
    "reverse the string",
    (y) => {
      y.positional("string", {
        type: "string",
        default: "string",
        describe: "string to reverse",
    (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", () => {

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", () => {

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


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

shadowtime2000's DEV profile image