citty-test-utils
Version:
A comprehensive testing framework for CLI applications built with Citty, featuring Docker cleanroom support, fluent assertions, advanced scenario DSL, and noun-verb CLI structure with template generation.
360 lines (270 loc) • 10.3 kB
Markdown
and compare CLI output deterministically. This feature is perfect for ensuring that CLI commands produce consistent output across different environments and over time.
- **Multiple Snapshot Types**: Support for stdout, stderr, JSON, full results, and custom data
- **Intelligent Normalization**: Automatic handling of timestamps, whitespace, and dynamic content
- **Fluent API Integration**: Seamless integration with existing assertion methods
- **Scenario DSL Support**: Snapshot testing within complex test scenarios
- **Pre-built Scenarios**: Ready-to-use snapshot testing patterns
- **Cross-Environment Consistency**: Same snapshots work in local and cleanroom environments
```javascript
import { runLocalCitty } from 'citty-test-utils'
// Test that help output matches expected snapshot
const result = await runLocalCitty(['--help'], { env: { TEST_CLI: 'true' } })
result
.expectSuccess()
.expectSnapshotStdout('help-output')
```
```javascript
// Different types of snapshots
result.expectSnapshotStdout('help-output') // stdout only
result.expectSnapshotStderr('error-output') // stderr only
result.expectSnapshotJson('api-response') // JSON data
result.expectSnapshotFull('complete-result') // full result object
result.expectSnapshotOutput('combined') // stdout + stderr
```
```javascript
import { scenario } from 'citty-test-utils'
const result = await scenario('Help snapshot test')
.step('Get help')
.run('--help', { env: { TEST_CLI: 'true' } })
.expectSuccess()
.expectSnapshotStdout('help-output')
.execute()
```
```javascript
const result = await scenario('Multi-step snapshot workflow')
.step('Initialize project')
.run('init', 'test-project', { env: { TEST_CLI: 'true' } })
.expectSuccess()
.snapshot('init-output')
.step('Check status')
.run('status', { env: { TEST_CLI: 'true' } })
.expectSuccess()
.snapshot('status-output', { type: 'full' })
.execute()
```
```javascript
import { SnapshotConfig, getSnapshotManager } from 'citty-test-utils'
const config = new SnapshotConfig({
snapshotDir: '__snapshots__', // Directory for snapshots
updateSnapshots: false, // Update snapshots on mismatch
ciMode: process.env.CI === 'true', // CI environment mode
ignoreWhitespace: true, // Normalize whitespace
ignoreTimestamps: true, // Remove timestamps
maxDiffSize: 1000 // Max diff size for errors
})
const manager = getSnapshotManager(config)
```
```javascript
// Different configurations for different environments
const config = new SnapshotConfig({
updateSnapshots: process.env.UPDATE_SNAPSHOTS === 'true',
ciMode: process.env.CI === 'true',
ignoreTimestamps: process.env.NODE_ENV === 'test'
})
```
```javascript
import { scenarios } from 'citty-test-utils'
// Help output snapshot
const helpResult = await scenarios.snapshotHelp({ env: { TEST_CLI: 'true' } }).execute()
// Version output snapshot
const versionResult = await scenarios.snapshotVersion({ env: { TEST_CLI: 'true' } }).execute()
// Error output snapshot
const errorResult = await scenarios.snapshotError({ env: { TEST_CLI: 'true' } }).execute()
// Full result snapshot
const fullResult = await scenarios.snapshotFull({ env: { TEST_CLI: 'true' } }).execute()
// Workflow snapshot
const workflowResult = await scenarios.snapshotWorkflow({ env: { TEST_CLI: 'true' } }).execute()
```
```javascript
import { matchSnapshot, snapshotUtils } from 'citty-test-utils'
const result = await runLocalCitty(['status'], { env: { TEST_CLI: 'true' } })
// Create custom snapshot data
const customData = {
exitCode: result.exitCode,
hasOutput: result.stdout.length > 0,
timestamp: new Date().toISOString()
}
// Match against custom snapshot
const snapshotResult = matchSnapshot(customData, __filename, 'custom-status')
if (!snapshotResult.match) {
throw new Error(snapshotResult.error)
}
```
```javascript
const result = await runLocalCitty(['--help'], { env: { TEST_CLI: 'true' } })
result
.expectSuccess()
.expectSnapshot('help-with-options', {
type: 'stdout',
testFile: __filename,
env: { TEST_CLI: 'true' },
cwd: process.cwd()
})
```
```javascript
import { SnapshotManager } from 'citty-test-utils'
const manager = new SnapshotManager()
// Generate snapshot key
const key = manager.generateKey('test-name', 'snapshot-name', {
args: ['--help'],
env: { TEST_CLI: 'true' },
cwd: process.cwd()
})
// Get snapshot path
const snapshotPath = manager.getSnapshotPath(__filename, 'snapshot-name')
// Load existing snapshot
const existingSnapshot = manager.loadSnapshot(snapshotPath)
// Save new snapshot
const snapshotData = {
data: 'snapshot content',
metadata: {
created: new Date().toISOString(),
testFile: __filename,
snapshotName: 'snapshot-name'
}
}
manager.saveSnapshot(snapshotPath, snapshotData)
```
```javascript
// Good: Descriptive names
result.expectSnapshotStdout('help-output')
result.expectSnapshotStderr('invalid-command-error')
result.expectSnapshotJson('status-response')
// Bad: Generic names
result.expectSnapshotStdout('snapshot1')
result.expectSnapshotStderr('error')
```
```javascript
// Group related snapshots
result.expectSnapshotStdout('help-output')
result.expectSnapshotStdout('help-output-detailed')
result.expectSnapshotStdout('help-output-json')
```
```javascript
// Use stdout for command output
result.expectSnapshotStdout('command-output')
// Use stderr for error messages
result.expectSnapshotStderr('error-message')
// Use json for structured data
result.expectSnapshotJson('api-response')
// Use full for complete result data
result.expectSnapshotFull('complete-result')
```
```javascript
// Configure to ignore timestamps and dynamic content
const config = new SnapshotConfig({
ignoreTimestamps: true,
ignoreWhitespace: true
})
const manager = getSnapshotManager(config)
```
```javascript
// Use environment variable to control snapshot updates
const config = new SnapshotConfig({
updateSnapshots: process.env.UPDATE_SNAPSHOTS === 'true'
})
```
1. **Snapshot Mismatch**: Check if the output has changed intentionally
2. **Missing Snapshots**: Ensure snapshots are created on first run
3. **Path Issues**: Verify test file paths are correct
4. **Permission Issues**: Check file system permissions for snapshot directory
```javascript
// Enable detailed logging
const manager = new SnapshotManager()
const result = manager.matchSnapshot(data, testFile, snapshotName, options)
if (!result.match) {
console.log('Snapshot mismatch details:', result.comparison)
console.log('Snapshot path:', result.snapshotPath)
}
```
```javascript
import { describe, it, expect } from 'vitest'
import { runLocalCitty } from 'citty-test-utils'
describe('CLI Snapshot Tests', () => {
it('should match help output snapshot', async () => {
const result = await runLocalCitty(['--help'], { env: { TEST_CLI: 'true' } })
result
.expectSuccess()
.expectSnapshotStdout('help-output')
})
})
```
```javascript
import { runLocalCitty } from 'citty-test-utils'
describe('CLI Snapshot Tests', () => {
test('should match help output snapshot', async () => {
const result = await runLocalCitty(['--help'], { env: { TEST_CLI: 'true' } })
result
.expectSuccess()
.expectSnapshotStdout('help-output')
})
})
```
Configuration options for snapshot testing:
- `snapshotDir`: Directory for storing snapshots (default: `__snapshots__`)
- `updateSnapshots`: Whether to update snapshots on mismatch (default: `false`)
- `ciMode`: CI environment mode (default: `process.env.CI === 'true'`)
- `ignoreWhitespace`: Normalize whitespace (default: `true`)
- `ignoreTimestamps`: Remove timestamps (default: `true`)
- `maxDiffSize`: Maximum diff size for error messages (default: `1000`)
### SnapshotManager
Core snapshot management functionality:
- `generateKey()`: Generate consistent snapshot keys
- `getSnapshotPath()`: Get snapshot file path
- `loadSnapshot()`: Load existing snapshot
- `saveSnapshot()`: Save snapshot to file
- `normalizeData()`: Normalize data for comparison
- `compareData()`: Compare data structures
- `matchSnapshot()`: Main snapshot matching function
### snapshotUtils
Utility functions for snapshot operations:
- `createSnapshotFromResult()`: Create snapshot from CLI result
- `createSnapshot()`: Create custom snapshot
- `validateSnapshot()`: Validate snapshot data
### Assertion Methods
Snapshot assertion methods available on result objects:
- `expectSnapshot()`: Generic snapshot assertion
- `expectSnapshotStdout()`: stdout snapshot assertion
- `expectSnapshotStderr()`: stderr snapshot assertion
- `expectSnapshotJson()`: JSON snapshot assertion
- `expectSnapshotFull()`: Full result snapshot assertion
- `expectSnapshotOutput()`: Combined output snapshot assertion
### Scenario DSL Methods
Snapshot methods available in scenario DSL:
- `expectSnapshot()`: Add snapshot expectation to step
- `expectSnapshotStdout()`: Add stdout snapshot expectation
- `expectSnapshotStderr()`: Add stderr snapshot expectation
- `expectSnapshotJson()`: Add JSON snapshot expectation
- `expectSnapshotFull()`: Add full result snapshot expectation
- `expectSnapshotOutput()`: Add combined output snapshot expectation
- `snapshot()`: Create snapshot step
This comprehensive snapshot testing system provides deterministic testing capabilities that ensure CLI output consistency across different environments and over time.
Citty-Test-Utils now includes comprehensive snapshot testing capabilities that allow you to capture