# Jest Basic CLI Testing Example This example demonstrates basic CLI testing patterns using Jest for Node.js/TypeScript projects. ## Setup ```bash npm install --save-dev jest @types/jest ts-jest @types/node ``` ## Test Structure ```typescript import { execSync } from 'child_process'; import path from 'path'; describe('CLI Tool Tests', () => { const CLI_PATH = path.join(__dirname, '../bin/mycli'); function runCLI(args: string) { try { const stdout = execSync(`${CLI_PATH} ${args}`, { encoding: 'utf8', stdio: 'pipe', }); return { stdout, stderr: '', code: 0 }; } catch (error: any) { return { stdout: error.stdout || '', stderr: error.stderr || '', code: error.status || 1, }; } } test('should display version', () => { const { stdout, code } = runCLI('--version'); expect(code).toBe(0); expect(stdout).toContain('1.0.0'); }); test('should display help', () => { const { stdout, code } = runCLI('--help'); expect(code).toBe(0); expect(stdout).toContain('Usage:'); }); test('should handle unknown command', () => { const { stderr, code } = runCLI('unknown-command'); expect(code).toBe(1); expect(stderr).toContain('unknown command'); }); }); ``` ## Running Tests ```bash # Run all tests npm test # Run with coverage npm run test:coverage # Run in watch mode npm run test:watch ``` ## Key Patterns ### 1. Command Execution Helper Create a reusable `runCLI()` function that: - Executes CLI commands using `execSync` - Captures stdout, stderr, and exit codes - Handles both success and failure cases ### 2. Exit Code Testing Always test exit codes: - `0` for success - Non-zero for errors - Specific codes for different error types ### 3. Output Validation Test output content using Jest matchers: - `.toContain()` for substring matching - `.toMatch()` for regex patterns - `.toBe()` for exact matches ### 4. Error Handling Test error scenarios: - Unknown commands - Invalid options - Missing required arguments - Invalid argument types ## Example Test Cases ```typescript describe('deploy command', () => { test('should deploy with valid arguments', () => { const { stdout, code } = runCLI('deploy production --force'); expect(code).toBe(0); expect(stdout).toContain('Deploying to production'); }); test('should fail without required arguments', () => { const { stderr, code } = runCLI('deploy'); expect(code).toBe(1); expect(stderr).toContain('missing required argument'); }); test('should validate environment names', () => { const { stderr, code } = runCLI('deploy invalid-env'); expect(code).toBe(1); expect(stderr).toContain('invalid environment'); }); }); ``` ## Best Practices 1. **Isolate Tests**: Each test should be independent 2. **Use Descriptive Names**: Test names should describe what they validate 3. **Test Both Success and Failure**: Cover happy path and error cases 4. **Mock External Dependencies**: Don't make real API calls or file system changes 5. **Use Type Safety**: Leverage TypeScript for better test reliability 6. **Keep Tests Fast**: Fast tests encourage frequent running ## Common Pitfalls - ❌ Not testing exit codes - ❌ Only testing success cases - ❌ Hardcoding paths instead of using `path.join()` - ❌ Not handling async operations properly - ❌ Testing implementation details instead of behavior ## Resources - [Jest Documentation](https://jestjs.io/docs/getting-started) - [Testing CLI Applications](https://jestjs.io/docs/cli) - [TypeScript with Jest](https://jestjs.io/docs/getting-started#using-typescript)