CLI Testlab - Test Framework for Node.js CLIs
cli-testlab is a specialized test framework designed for Node.js command-line interface (CLI) applications. Currently at stable version 6.0.1, it provides a streamlined API for executing shell commands, capturing their standard output and error streams, and asserting on their content. Key features include the `execCommand` function, which integrates robust assertion capabilities for positive, negative, and error-based output checks, supporting both string and array inputs for comprehensive validation. It also facilitates the management of environment variables per command execution and offers a `FileTestHelper` class for automatic cleanup of test-generated files. This library differentiates itself by focusing specifically on the unique challenges of CLI testing, offering built-in utilities that abstract away common complexities like child process management and output parsing, making it simpler to write reliable and maintainable tests for CLI tools without relying on heavy general-purpose test runners for these specific tasks.
Common errors
-
TypeError: (0 , cli_testlab__WEBPACK_IMPORTED_MODULE_0__.execCommand) is not a function
cause Attempting to use CommonJS `require()` syntax or an outdated bundler configuration that struggles with ESM named exports, despite the package being ESM-first.fixEnsure your project is configured for ESM, typically by setting `"type": "module"` in `package.json`, and use `import { execCommand } from 'cli-testlab'`. If using a bundler like Webpack or Rollup, ensure its configuration properly handles ESM modules. -
Error: Command 'node my-cli.js unknown-command' failed with exit code 1 and output: '...' (stderr: '...')
cause The `execCommand` function throws an error if the executed command exits with a non-zero status code and no `expectedErrorMessage` is provided.fixIf the command is expected to fail, add `expectedErrorMessage: 'Partial or full error message'` to the `execCommand` options. If it's *not* expected to fail, investigate the CLI command and its environment (`baseDir`, `env`) to understand why it's returning a non-zero exit code. -
AssertionError: Expected output 'Expected text' was not found in stdout.
cause The `expectedOutput` assertion failed because the specified text was not present in the command's standard output.fixVerify the exact output of your CLI command (e.g., by running it manually or inspecting `result.stdout` in debug) and adjust the `expectedOutput` string to precisely match. Remember that assertions are case-sensitive and whitespace-sensitive. -
Error: spawn <command> ENOENT
cause The shell could not find the specified command. This often happens if the command is not in the system's PATH, or the path to a Node.js script is incorrect.fixEnsure the command string is correct and includes the necessary prefix (e.g., `node` for Node.js scripts). Check the `baseDir` option to confirm the working directory for the command execution is correct and the target script/executable exists at that relative path. If it's a globally installed binary, ensure your test runner environment has the correct PATH configured.
Warnings
- breaking Major version 6 likely introduced breaking changes. While specific changes aren't detailed in the provided README, users upgrading from v5 or earlier should review the official changelog for API shifts, particularly regarding import paths, options for `execCommand`, or assertion behavior.
- gotcha `execCommand` is an asynchronous function and must always be `await`ed. Forgetting `await` will result in tests passing prematurely or unexpected behavior, as assertions will not be evaluated.
- gotcha cli-testlab has a minimum Node.js version requirement of 18. Running tests with older Node.js versions will lead to errors, potentially including syntax errors for modern JavaScript features or module resolution failures.
- gotcha Assertions for successful commands (`expectedOutput`, `notExpectedOutput`) are checked against `stdout`, while `expectedErrorMessage` is checked against `stderr` specifically when a command exits with a non-zero code. Mixing these or using `expectedErrorMessage` for a successful command will not work as expected.
- gotcha Forgetting to use `FileTestHelper.cleanup()` or manually clean up test-generated files can lead to 'polluted' test environments, causing flaky tests or consuming excessive disk space. While `FileTestHelper` automates this, it still needs to be explicitly invoked.
Install
-
npm install cli-testlab -
yarn add cli-testlab -
pnpm add cli-testlab
Imports
- execCommand
const { execCommand } = require('cli-testlab')import { execCommand } from 'cli-testlab' - FileTestHelper
const FileTestHelper = require('cli-testlab').FileTestHelperimport { FileTestHelper } from 'cli-testlab' - execCommand default
import cliTestlab from 'cli-testlab'; const { execCommand } = cliTestlab;
Quickstart
import { execCommand, FileTestHelper } from 'cli-testlab';
import { promises as fs } from 'node:fs';
import { resolve } from 'node:path';
describe('My CLI application', () => {
let fileHelper: FileTestHelper;
beforeEach(() => {
fileHelper = new FileTestHelper('temp-test-dir');
});
afterEach(async () => {
await fileHelper.cleanup();
});
it('should report version correctly', async () => {
// Assuming 'my-cli.js' is in the same directory as the test file for simplicity
// In a real project, you might use 'path.resolve' to locate it.
await execCommand('node my-cli.js --version', {
expectedOutput: '1.0.0',
baseDir: process.cwd() // Or the directory where my-cli.js resides
});
});
it('should show an error for unknown commands', async () => {
await execCommand('node my-cli.js unknown-command', {
expectedErrorMessage: 'Unknown command',
baseDir: process.cwd()
});
});
it('should create a file and clean it up', async () => {
const testFilePath = fileHelper.createFile('test.txt', 'hello world');
// Assume your CLI has a command like 'my-cli.js process-file test.txt'
await execCommand(`node my-cli.js process-file ${testFilePath}`, {
expectedOutput: 'File processed'
});
// Verify the file exists before cleanup
await expect(fs.access(resolve(fileHelper.tempDir, 'test.txt'))).resolves.toBeUndefined();
});
});