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 processesprocess.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.