Coverage thresholds and reporting. Use when analyzing and improving test coverage.
This skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill covers test coverage standards and analysis.
Use this skill when:
COVERAGE IS A MINIMUM, NOT A GOAL - 80% coverage is a floor, not a ceiling. Focus on testing critical paths.
| Metric | Threshold |
|---|---|
| Lines | 80% |
| Functions | 80% |
| Branches | 80% |
| Statements | 80% |
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
provider: 'v8',
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
},
});
Percentage of executable lines that were run during tests.
function example(x: number): number {
if (x > 0) { // Line 1
return x * 2; // Line 2 - only covered if x > 0
}
return 0; // Line 3 - only covered if x <= 0
}
Percentage of decision branches (if/else, switch, ternary) that were taken.
function example(x: number): string {
// Two branches: true and false
if (x > 0) {
return 'positive'; // Branch 1
} else {
return 'non-positive'; // Branch 2
}
}
// Need both tests for 100% branch coverage
test('positive', () => expect(example(1)).toBe('positive'));
test('non-positive', () => expect(example(0)).toBe('non-positive'));
Percentage of functions that were called at least once.
Percentage of statements that were executed.
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
exclude: [
'node_modules/',
'dist/',
'**/*.test.ts',
'**/__tests__/**',
'**/__mocks__/**',
'**/*.d.ts',
'**/types/**',
'**/index.ts', // Re-export files
'**/generated/**', // Generated code
],
},
},
});
/* v8 ignore next */
function debugOnly(): void {
console.log('debug');
}
/* v8 ignore start */
function untestableCode(): void {
// Platform-specific code
}
/* v8 ignore stop */
export default defineConfig({
test: {
coverage: {
reporter: [
'text', // Terminal output
'html', // HTML report
'json', // JSON for tools
'lcov', // For coverage services
'cobertura', // For CI systems
],
reportsDirectory: './coverage',
},
},
});
# Generate coverage
npm run test:coverage
# View HTML report
open coverage/index.html
// Often uncovered
function fetchData(): Promise<Data> {
try {
return await api.get('/data');
} catch (error) {
// This branch often uncovered
throw new ApiError('Failed to fetch');
}
}
// Test the error path
it('should throw on API error', async () => {
vi.mocked(api.get).mockRejectedValue(new Error('Network'));
await expect(fetchData()).rejects.toThrow('Failed to fetch');
});
function divide(a: number, b: number): number {
if (b === 0) {
throw new Error('Division by zero'); // Often uncovered
}
return a / b;
}
// Test edge case
it('should throw for division by zero', () => {
expect(() => divide(1, 0)).toThrow('Division by zero');
});
function processUser(user: User | null): string {
if (!user) {
return 'No user'; // Test this branch
}
return user.name;
}
// ❌ Bad - tests internal state
it('should set internal flag', () => {
service.process();
expect(service._internalFlag).toBe(true);
});
// ✅ Good - tests behavior
it('should produce expected output', () => {
const result = service.process();
expect(result).toEqual(expected);
});
// ❌ Bad - covers code but doesn't verify
it('should run without errors', () => {
const result = processData(input);
// No assertions!
});
// ✅ Good - verifies behavior
it('should transform data correctly', () => {
const result = processData(input);
expect(result).toEqual(expectedOutput);
});
// Not everything needs testing
/* v8 ignore next */
if (process.env.DEBUG) {
console.log('Debug info');
}
- name: Run tests with coverage
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./coverage/lcov.info
fail_ci_if_error: true
- name: Coverage Report
uses: davelosert/vitest-coverage-report-action@v2