How to Mock Commander.js in Jest (for Senior Developers)

When writing unit tests in a Node.js application, external dependencies like commander can sometimes get in the way. If you're using Commander.js for CLI interfaces, you'll likely want to mock it in Jest to isolate logic and avoid parsing CLI arguments during test runs.

In this article, we’ll walk through how to mock Commander.js with Jest, covering key techniques and code examples to help you keep your tests clean, fast, and focused.

📦 What Is Commander.js?

Commander.js is a popular npm package for building command-line interfaces (CLIs) in Node.js. It provides an intuitive, fluent API for parsing arguments and defining commands:

const { program } = require('commander');

program
  .option('-d, --debug', 'output extra debugging')
  .option('-p, --port <number>', 'port number');

program.parse(process.argv);
const options = program.opts();

🎯 Why Mock Commander in Jest?

When unit testing CLI tools, you don’t want to depend on actual CLI input. Instead, you should:

  • Avoid calling program.parse(), which processes process.argv
  • Mock Commander’s API to simulate user input
  • Focus your test on the behavior of your app, not the library

✅ How to Mock Commander.js in Jest

Let’s say you have a CLI file like this:

// cli.js
const { program } = require('commander');

program
  .option('--verbose', 'Enable verbose logging')
  .parse(process.argv);

const options = program.opts();

module.exports = {
  runApp: () => {
    if (options.verbose) {
      console.log('Verbose mode enabled');
    }
  }
};

🧪 Step 1: Create the Jest Test File

To mock commander, you can use Jest's jest.mock and jest.fn() APIs. Here’s how:

// cli.test.js
jest.mock('commander', () => {
  const opts = { verbose: true }; // simulate CLI input
  return {
    program: {
      option: jest.fn().mockReturnThis(),
      parse: jest.fn().mockReturnThis(),
      opts: jest.fn(() => opts),
    }
  };
});

const { runApp } = require('./cli');

describe('CLI tool', () => {
  it('should log when verbose is true', () => {
    console.log = jest.fn();

    runApp();

    expect(console.log).toHaveBeenCalledWith('Verbose mode enabled');
  });
});

🛠️ Notes for Advanced Use Cases

1. Simulating Multiple Option Scenarios

You can simulate different inputs by adjusting opts dynamically:

jest.mock('commander', () => {
  let mockOpts = {};

  return {
    program: {
      option: jest.fn().mockReturnThis(),
      parse: jest.fn().mockReturnThis(),
      opts: jest.fn(() => mockOpts),
    },
    __setOpts: (opts) => { mockOpts = opts; },
  };
});

In your test:

const commander = require('commander');
const { runApp } = require('./cli');

test('runApp logs when verbose is true', () => {
  commander.__setOpts({ verbose: true });
  console.log = jest.fn();

  runApp();

  expect(console.log).toHaveBeenCalledWith('Verbose mode enabled');
});

2. Resetting Mocks Between Tests

Always reset the mock implementation if you mutate shared state:

afterEach(() => {
  jest.clearAllMocks();
});

🧩 Bonus: Mocking Commander in ES Module Context

If you’re using ESM ("type": "module" in package.json), mocking becomes trickier. Use the jest.mock() with async import or rely on a mock shim module.


🚀 Conclusion

Mocking Commander.js with Jest is straightforward and powerful. It helps isolate your logic from CLI parsing and keeps your tests predictable.

🔑 Key Takeaways

  • Avoid calling program.parse(process.argv) during tests
  • Mock the .option(), .parse(), and .opts() methods
  • Simulate user inputs by returning custom opts in your mock

Now you’re ready to confidently test any CLI tool using Commander.js — no process.argv required.