8.2 KiB
Reproduction Techniques
Reliable reproduction is the foundation of effective debugging. If you can't reproduce the bug consistently, you can't verify your fix works.
Minimal Reproduction
Goal: Smallest possible code that demonstrates the bug.
Process
- Start with full failing case
- Remove one thing at a time
- After each removal, verify bug still occurs
- Continue until nothing else can be removed
- Result: minimal reproduction case
Example
Initial failing case (500 lines):
// Complex app with many features
// Bug: Login fails
Minimal reproduction (15 lines):
import { authenticate } from './auth';
// Bug occurs when password contains special chars
const result = await authenticate({
username: 'test@example.com',
password: 'p@ssw0rd!',
});
// Expected: success
// Actual: fails with "Invalid credentials"
Benefits
- Isolates exact cause
- Eliminates red herrings
- Makes debugging tractable
- Helps others reproduce
- Creates focused test case
Reproduction Checklist
Create checklist for consistent reproduction:
## Environment
- [ ] OS/platform: macOS 14.1
- [ ] Node version: 20.10.0
- [ ] Package versions: see package.json
- [ ] Environment variables: NODE_ENV=production
## Setup
- [ ] Database state: Empty database with schema v2.3
- [ ] File system state: No cache files
- [ ] Configuration: Default config.json
- [ ] Prerequisites: Redis running on localhost:6379
## Steps to Reproduce
1. [ ] Start server: `npm run start`
2. [ ] Navigate to `/login`
3. [ ] Enter credentials with special chars in password
4. [ ] Click "Login"
## Expected vs Actual
**Expected**: User logged in successfully
**Actual**: Error message "Invalid credentials" (password is correct)
## Additional Context
- Bug does NOT occur with alphanumeric passwords
- Bug started after upgrading bcrypt from 5.0.0 to 5.1.0
- Affects 3% of login attempts based on logs
Template
## Environment
- [ ] OS/platform: _____
- [ ] Language/runtime version: _____
- [ ] Dependency versions: _____
- [ ] Environment variables: _____
## Setup
- [ ] Database state: _____
- [ ] File system state: _____
- [ ] Configuration: _____
- [ ] Prerequisites: _____
## Steps to Reproduce
1. [ ] _____
2. [ ] _____
3. [ ] _____
## Expected vs Actual
**Expected**: _____
**Actual**: _____
## Additional Context
- _____
Automated Reproduction
Convert manual steps to automated test.
Benefits
- Runs in CI/CD
- Documents exact conditions
- Verifies fix automatically
- Prevents regression
Example: Manual to Automated
Manual steps:
- Create user with ID "test-123"
- Set user email to null
- Call getUserDisplay(user)
- Observe crash
Automated test:
describe('getUserDisplay', () => {
it('reproduces crash with null email', () => {
// Setup
const userWithNullEmail = {
id: 'test-123',
name: 'Test User',
email: null, // This triggers the bug
};
// Execute - currently crashes
expect(() => getUserDisplay(userWithNullEmail)).toThrow(
TypeError // Will be fixed to throw proper validation error
);
});
});
After fix:
expect(() => getUserDisplay(userWithNullEmail)).toThrow(
'User email is required'
);
Reproduction Patterns by Bug Type
Runtime Errors
Focus on input values:
// Reproduce with specific input that triggers error
const problematicInput = {
value: undefined, // Causes crash
nested: { field: null },
};
expect(() => process(problematicInput)).toThrow(TypeError);
Logic Bugs
Focus on edge cases:
// Reproduce with boundary conditions
expect(calculateTotal([])).toBe(0); // Empty array
expect(calculateTotal([5])).toBe(5); // Single item
expect(calculateTotal([5, -3])).toBe(2); // Negative values
expect(calculateTotal([0.1, 0.2])).toBe(0.3); // Floating point
Integration Failures
Mock external dependencies:
// Reproduce API failure
const mockApi = {
fetchUser: vi.fn().mockRejectedValue(
new Error('API timeout')
),
};
await expect(
getUserProfile('123', mockApi)
).rejects.toThrow('Failed to fetch user');
Intermittent Issues
Add timing/concurrency:
// Reproduce race condition
const results = await Promise.all([
updateUser('123', { name: 'Alice' }),
updateUser('123', { name: 'Bob' }),
]);
// One update should fail or last write should win consistently
expect(results.filter(r => r.success)).toHaveLength(1);
Performance Issues
Reproduce with scale:
// Reproduce performance degradation
const largeDataset = Array.from(
{ length: 10000 },
(_, i) => ({ id: i, data: 'x'.repeat(1000) })
);
const startTime = Date.now();
const result = processData(largeDataset);
const duration = Date.now() - startTime;
// Should complete in reasonable time
expect(duration).toBeLessThan(1000); // 1 second
Flaky Test Handling
When test sometimes passes, sometimes fails:
Techniques
Run multiple times:
# Run test 100 times to find pattern
for i in {1..100}; do
npm test -- --grep "flaky test" || echo "Failed on run $i"
done
Add delays to expose timing:
// If suspected race condition
await new Promise(resolve => setTimeout(resolve, 100));
// See if consistent delay changes behavior
Check for shared state:
// Isolate test with fresh setup
beforeEach(() => {
// Reset all state
clearCache();
resetDatabase();
clearEventListeners();
});
Log timing information:
console.log(`[${new Date().toISOString()}] Step 1 completed`);
console.log(`[${new Date().toISOString()}] Step 2 completed`);
// Look for timing patterns in failures
Reproduction in Different Environments
Bugs may only occur in specific environments.
Environment Matrix
Test across:
- Operating systems (macOS, Linux, Windows)
- Runtime versions (Node 18, 20, 22)
- Dependency versions (latest, locked)
- Environment modes (dev, staging, production)
Docker Reproduction
Ensure consistent environment:
FROM node:20.10.0
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Reproduce bug
RUN npm test -- --grep "bug reproduction"
Benefits:
- Consistent across machines
- Documents exact environment
- Easy for others to reproduce
Documentation
When sharing reproduction:
Include
- Exact steps — numbered, detailed
- Expected behavior — what should happen
- Actual behavior — what actually happens
- Environment details — versions, config
- Minimal code — smallest failing example
- Screenshots/logs — visual confirmation
Template
# Bug: {Brief Description}
## Reproduction
**Environment:**
- OS: macOS 14.1
- Runtime: Node.js 20.10.0
- Dependencies: see lockfile commit abc123
**Steps:**
1. Clone repo at commit abc123
2. Run `npm install`
3. Run `npm test -- --grep "specific test"`
4. Observe failure
**Expected:** Test passes
**Actual:** Test fails with "TypeError: ..."
**Minimal code:**
\`\`\`typescript
// 10 lines that trigger bug
\`\`\`
**Logs:**
\`\`\`
[full error output]
\`\`\`
## Additional Context
- Fails 100% of time with these steps
- Does not fail if X is changed to Y
- Started after commit abc123
Common Pitfalls
Non-deterministic Reproduction
Problem: Can't reproduce consistently
Solutions:
- Control randomness (seed random number generators)
- Control timing (use fixed delays, not timeouts)
- Control environment (Docker, locked dependencies)
- Control input (save exact input that triggers bug)
Over-complex Reproduction
Problem: Reproduction requires too much setup
Solutions:
- Simplify to minimal case
- Mock external dependencies
- Use in-memory databases for tests
- Extract core logic that fails
Environment-specific Bugs
Problem: "Works on my machine"
Solutions:
- Document exact environment (Docker)
- Check for environment variables
- Verify dependency versions match
- Test on clean install
Summary
Reliable reproduction is critical for:
- Understanding the bug
- Verifying the fix
- Preventing regression
- Communicating the issue
Time invested in solid reproduction saves time in debugging and verification.