146 lines
3.6 KiB
Markdown
146 lines
3.6 KiB
Markdown
# 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)
|