Test Coverage
Purpose: Ensure test coverage for changed code Phases: Analyze → Discover → Baseline → Design → Write → Run → Measure → Report Usage:
/test-coverage [scope flags]
Iron Laws
- EVERY CHANGED FUNCTION NEEDS A TEST — No exceptions for "simple" code. Simple code that breaks causes the worst outages because nobody thought to test it.
- TESTS MUST BE INDEPENDENT — No shared mutable state, no test ordering dependencies. Every test must pass when run alone.
- TEST BEHAVIOR, NOT IMPLEMENTATION — Test what the code does, not how it does it. Refactoring should not break tests.
When to Use
- After implementing a feature (post
/implement) - When adding tests to existing uncovered code
- Before submitting a PR to ensure coverage
- When a bug fix needs regression tests
When NOT to Use
- Writing tests before code →
/tdd - Debugging a test failure →
/debug - Full CI validation →
/validate
Constraints
- Create test files (
.spec.ts,.test.ts) freely - Do not modify non-test source files without approval
- Scope tests to changes — don't run full suites unless necessary
- Every test file MUST include a test plan as a comment
Note: Command examples use
npmas default. Adapt to the project's package manager perai-assistant-protocol— Project Commands.
Scope Flags
| Flag | Description |
|---|---|
--files=<paths> |
Specific files/directories to check coverage for |
--branch=<name> |
Compare against specific branch (default: main) |
--uncommitted |
Cover only uncommitted changes |
Test Quality Criteria
| Criterion | Rule | Smell if Violated |
|---|---|---|
| Independent | No shared mutable state between tests | Tests pass alone but fail together |
| Fast | Mock external dependencies (DB, API, filesystem) | Suite takes minutes |
| Readable | Clear Arrange/Act/Assert structure | Can't understand without reading source |
| Focused | One behavior per test | Test name contains "and" |
| Deterministic | Same input → same output | Flaky tests |
Don't Test
- Types / interfaces — no runtime behavior
- Trivial getters/setters — one-line property access with no logic
- Framework internals — React rendering, Express routing itself
- Constants / enums — static values
- Generated code — Prisma client, GraphQL codegen
Test Smell Detection
| Smell | Fix |
|---|---|
| Testing implementation details (spying on private methods) | Test the public API output |
| Multi-concern tests (name has "and") | Split into focused tests |
| Mirror tests (structure mirrors implementation) | Test inputs/outputs |
| No meaningful assertions (only checks no error thrown) | Assert on return values or side effects |
| Testing the mock (assertions only on mock calls) | Assert on behavior the mock enables |
| Coverage theater (tests execute code without meaningful assertions) | Add real assertions or delete the test |
Coverage Targets
| File Type | Target | Focus |
|---|---|---|
| Business logic / services | 80%+ | Edge cases, error paths |
| Utilities / helpers | 90%+ | All code paths |
| API routes / handlers | 70%+ | Happy path + error codes |
| UI components | 60%+ | User interactions, states |
Workflow
Step 1: Analyze Changes
MAIN_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")
git diff --name-only $MAIN_BRANCH..HEAD
Step 2: Categorize and Identify Missing Coverage
| File Type | Test Type | Priority |
|---|---|---|
*.ts utilities/services |
Unit tests (.spec.ts) |
High |
*.tsx components |
Component tests | High |
*.ts types/interfaces |
Skip | — |
| Config/build files | Skip | — |
Early exit: If all changed files are type-only (interfaces, type definitions, enums, constants), config-only, or already have adequate test coverage, report this and exit:
## Nothing to Test
All changed files are either type-only definitions or already have adequate coverage:
- `types/user.ts` — type definitions only (skip)
- `services/auth.service.ts` — existing tests cover changes (skip)
No new tests needed.
Step 3: Discover Test Patterns
Before writing any tests, discover where existing tests live and what patterns they use:
- File naming: Search for
*.spec.ts,*.test.ts,*.spec.tsx,*.test.tsxto determine the project convention - Directory structure: Check if tests are co-located with source files or in separate
__tests__/directories - Import patterns: Note how modules under test are imported (relative paths, aliases, barrel imports)
- Mock patterns: Identify how dependencies are mocked (manual mocks,
vi.mock(),jest.mock(), dependency injection)
Read 1-2 existing test files that are closest to the files being tested (same directory or same file type) and adopt their:
describe/itnesting structure- Setup/teardown patterns (
beforeEach,afterEach, factories) - Assertion style (
expect().toBe(),expect().toEqual(), custom matchers) - Mock approach (inline mocks, shared fixtures, mock factories)
If no existing test files are found, use the default structure in Step 6.
Step 4: Measure Coverage Baseline
Check if the project has coverage tooling configured:
- Look for coverage configuration in
vitest.config.*,jest.config.*,package.json(jest/vitest sections), or.nycrc - Check
package.jsonscripts for a coverage command (e.g.,test:coverage,coverage)
If coverage tooling is available, run coverage on affected files to establish a baseline:
# Vitest example
npm run test -- --coverage --run <affected-pattern>
# Jest example
npm run test -- --coverage --collectCoverageFrom='<affected-pattern>' --forceExit
Record the baseline coverage percentages for affected files. If no coverage tooling exists, note this in the final report and proceed without numeric measurements.
Step 5: Design Test Plans
For each file needing tests:
## Test Plan: [ModuleName]
| Function | Behaviors | Edge Cases |
|----------|-----------|------------|
| `functionA` | happy path, error path | null input, empty array |
Step 6: Write Tests
Required test plan (Gherkin) as comment:
/**
* Test Plan: ModuleName
*
* Scenario: Brief description
* Given [initial state]
* When [action]
* Then [expected outcome]
*/
Test structure (default — override with patterns discovered in Step 3):
describe('ModuleName', () => {
describe('functionName', () => {
it('should [expected behavior] when [condition]', () => {
// Arrange
// Act
// Assert
});
});
});
Coverage priorities: Happy path → Edge cases (null, empty, boundary) → Error conditions → Async operations
For utilities with well-defined contracts, consider property-based testing (e.g., with fast-check) to catch edge cases that example-based tests miss.
Step 7: Run and Fix
npm run test -- path/to/file.spec.ts
If failures: fix mocks, assertions, missing await, or isolation issues. Re-run until green.
Step 8: Measure Coverage After
If coverage tooling was available in Step 4, re-run coverage to show improvement:
npm run test -- --coverage --run <affected-pattern>
Compare before vs after for each affected file. Record the delta.
If coverage tooling is not available, skip this step — the report in Step 9 will note that numeric coverage was not measurable.
Step 9: Report and Approve
Present the coverage report and wait for user approval before committing.
## Test Coverage Report
### Coverage Summary (if measurable)
| File | Before | After | Delta |
|------|--------|-------|-------|
| `services/user.service.ts` | 12% | 85% | +73% |
| `utils/validator.ts` | 0% | 92% | +92% |
### Tests Created
- `user.service.spec.ts` — 8 tests, all passing
- `validator.spec.ts` — 5 tests, all passing
### Skipped (No Tests Needed)
- `types.ts` — type definitions only
### Remaining Gaps
- `user.service.ts` line 45-52: error recovery branch (edge case, low risk)
### Test Quality Check
| Criterion | Status |
|-----------|--------|
| Independent | Pass |
| Fast | Pass |
| Focused | Pass |
| Deterministic | Pass |
GATE: Do NOT commit until user responds with explicit approval. See ai-assistant-protocol for valid approval terms and invalid responses.
Acceptance Tests
| ID | Type | Prompt / Condition | Expected |
|---|---|---|---|
| COV-T1 | Positive | "Add tests for this code" | Skill triggers |
| COV-T2 | Positive | "Ensure test coverage for my changes" | Skill triggers |
| COV-T3 | Positive | "These files need tests" | Skill triggers |
| COV-T4 | Negative | "Write the test first, then implement" | Does NOT trigger (-> /tdd) |
| COV-T5 | Negative | "Run the test suite" | Does NOT trigger (-> /validate) |
| COV-T6 | Negative | "Debug the failing test" | Does NOT trigger (-> /debug) |
| COV-T7 | Boundary | "This function needs a test" | Triggers (adding coverage to existing code) |
| COV-T8 | Early-exit | All changed files are type-only or already covered | Reports "No new tests needed" and exits |
Quick Reference
| Phase | Gate |
|---|---|
| 1. Analyze | — |
| 2. Categorize | Early exit if nothing to test |
| 3. Discover Patterns | — |
| 4. Baseline Coverage | — |
| 5. Design | — |
| 6. Write | — |
| 7. Run | All tests pass |
| 8. Measure Coverage | — |
| 9. Report & Approve | User approves before commit |
Converted and distributed by TomeVault — claim your Tome and manage your conversions.