Initial commit
This commit is contained in:
277
skills/cli-testing-patterns/examples/jest-advanced/README.md
Normal file
277
skills/cli-testing-patterns/examples/jest-advanced/README.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# Jest Advanced CLI Testing Example
|
||||
|
||||
Advanced testing patterns for CLI applications including mocking, fixtures, and integration tests.
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### 1. Async Command Testing
|
||||
|
||||
```typescript
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
async function runCLIAsync(args: string[]): Promise<CLIResult> {
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn(CLI_PATH, args, { stdio: 'pipe' });
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
child.stdout?.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
child.stderr?.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
resolve({ stdout, stderr, code: code || 0 });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('should handle long-running command', async () => {
|
||||
const result = await runCLIAsync(['deploy', 'production']);
|
||||
expect(result.code).toBe(0);
|
||||
}, 30000); // 30 second timeout
|
||||
```
|
||||
|
||||
### 2. Environment Variable Mocking
|
||||
|
||||
```typescript
|
||||
describe('environment configuration', () => {
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
afterEach(() => {
|
||||
process.env = { ...originalEnv };
|
||||
});
|
||||
|
||||
test('should use API key from environment', () => {
|
||||
process.env.API_KEY = 'test_key_123';
|
||||
const { stdout, code } = runCLI('status');
|
||||
expect(code).toBe(0);
|
||||
expect(stdout).toContain('Authenticated');
|
||||
});
|
||||
|
||||
test('should fail without API key', () => {
|
||||
delete process.env.API_KEY;
|
||||
const { stderr, code } = runCLI('status');
|
||||
expect(code).toBe(1);
|
||||
expect(stderr).toContain('API key not found');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 3. File System Fixtures
|
||||
|
||||
```typescript
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
|
||||
describe('config file handling', () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-test-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('should create config file', () => {
|
||||
const configFile = path.join(tempDir, '.config');
|
||||
const result = runCLI(`init --config ${configFile}`);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(fs.existsSync(configFile)).toBe(true);
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
||||
expect(config).toHaveProperty('api_key');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Mocking External APIs
|
||||
|
||||
```typescript
|
||||
import nock from 'nock';
|
||||
|
||||
describe('API interaction', () => {
|
||||
beforeEach(() => {
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
test('should fetch deployment status', () => {
|
||||
nock('https://api.example.com')
|
||||
.get('/deployments/123')
|
||||
.reply(200, { status: 'success', environment: 'production' });
|
||||
|
||||
const { stdout, code } = runCLI('status --deployment 123');
|
||||
expect(code).toBe(0);
|
||||
expect(stdout).toContain('success');
|
||||
expect(stdout).toContain('production');
|
||||
});
|
||||
|
||||
test('should handle API errors', () => {
|
||||
nock('https://api.example.com')
|
||||
.get('/deployments/123')
|
||||
.reply(500, { error: 'Internal Server Error' });
|
||||
|
||||
const { stderr, code } = runCLI('status --deployment 123');
|
||||
expect(code).toBe(1);
|
||||
expect(stderr).toContain('API error');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Test Fixtures
|
||||
|
||||
```typescript
|
||||
// test-fixtures.ts
|
||||
export const createTestFixtures = () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-test-'));
|
||||
|
||||
// Create sample project structure
|
||||
fs.mkdirSync(path.join(tempDir, 'src'));
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'package.json'),
|
||||
JSON.stringify({ name: 'test-project', version: '1.0.0' })
|
||||
);
|
||||
|
||||
return {
|
||||
tempDir,
|
||||
cleanup: () => fs.rmSync(tempDir, { recursive: true, force: true }),
|
||||
};
|
||||
};
|
||||
|
||||
// Usage in tests
|
||||
test('should build project', () => {
|
||||
const fixtures = createTestFixtures();
|
||||
|
||||
try {
|
||||
const result = runCLI(`build --cwd ${fixtures.tempDir}`);
|
||||
expect(result.code).toBe(0);
|
||||
expect(fs.existsSync(path.join(fixtures.tempDir, 'dist'))).toBe(true);
|
||||
} finally {
|
||||
fixtures.cleanup();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 6. Snapshot Testing
|
||||
|
||||
```typescript
|
||||
test('help output matches snapshot', () => {
|
||||
const { stdout } = runCLI('--help');
|
||||
expect(stdout).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('version format matches snapshot', () => {
|
||||
const { stdout } = runCLI('--version');
|
||||
expect(stdout).toMatchSnapshot();
|
||||
});
|
||||
```
|
||||
|
||||
### 7. Parameterized Tests
|
||||
|
||||
```typescript
|
||||
describe.each([
|
||||
['development', 'dev.example.com'],
|
||||
['staging', 'staging.example.com'],
|
||||
['production', 'api.example.com'],
|
||||
])('deploy to %s', (environment, expectedUrl) => {
|
||||
test(`should deploy to ${environment}`, () => {
|
||||
const { stdout, code } = runCLI(`deploy ${environment}`);
|
||||
expect(code).toBe(0);
|
||||
expect(stdout).toContain(expectedUrl);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 8. Interactive Command Testing
|
||||
|
||||
```typescript
|
||||
import { Readable, Writable } from 'stream';
|
||||
|
||||
test('should handle interactive prompts', (done) => {
|
||||
const child = spawn(CLI_PATH, ['init'], { stdio: 'pipe' });
|
||||
|
||||
const inputs = ['my-project', 'John Doe', 'john@example.com'];
|
||||
let inputIndex = 0;
|
||||
|
||||
child.stdout?.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
if (output.includes('?') && inputIndex < inputs.length) {
|
||||
child.stdin?.write(inputs[inputIndex] + '\n');
|
||||
inputIndex++;
|
||||
}
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
expect(code).toBe(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 9. Coverage-Driven Testing
|
||||
|
||||
```typescript
|
||||
// Ensure all CLI commands are tested
|
||||
describe('CLI command coverage', () => {
|
||||
const commands = ['init', 'build', 'deploy', 'status', 'config'];
|
||||
|
||||
commands.forEach((command) => {
|
||||
test(`${command} command exists`, () => {
|
||||
const { stdout } = runCLI('--help');
|
||||
expect(stdout).toContain(command);
|
||||
});
|
||||
|
||||
test(`${command} has help text`, () => {
|
||||
const { stdout, code } = runCLI(`${command} --help`);
|
||||
expect(code).toBe(0);
|
||||
expect(stdout).toContain('Usage:');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 10. Performance Testing
|
||||
|
||||
```typescript
|
||||
test('command executes within time limit', () => {
|
||||
const startTime = Date.now();
|
||||
const { code } = runCLI('status');
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
expect(code).toBe(0);
|
||||
expect(duration).toBeLessThan(2000); // Should complete within 2 seconds
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Test Fixtures**: Create reusable test data and cleanup functions
|
||||
2. **Mock External Dependencies**: Never make real API calls or database connections
|
||||
3. **Test Edge Cases**: Test boundary conditions, empty inputs, special characters
|
||||
4. **Async Handling**: Use proper async/await or promises for async operations
|
||||
5. **Cleanup**: Always cleanup temp files, reset mocks, restore environment
|
||||
6. **Isolation**: Tests should not depend on execution order
|
||||
7. **Clear Error Messages**: Write assertions with helpful failure messages
|
||||
|
||||
## Common Advanced Patterns
|
||||
|
||||
- Concurrent execution testing
|
||||
- File locking and race conditions
|
||||
- Signal handling (SIGTERM, SIGINT)
|
||||
- Large file processing
|
||||
- Streaming output
|
||||
- Progress indicators
|
||||
- Error recovery and retry logic
|
||||
|
||||
## Resources
|
||||
|
||||
- [Jest Advanced Features](https://jestjs.io/docs/advanced)
|
||||
- [Mocking with Jest](https://jestjs.io/docs/mock-functions)
|
||||
- [Snapshot Testing](https://jestjs.io/docs/snapshot-testing)
|
||||
Reference in New Issue
Block a user