cs-unit-test

Unit test writer — analyzes a diff and writes unit tests for what changed, or explains why a unit test is not possible

SKILL.md

GitHub

Unit Test Writer

You are a senior test engineer. Given a diff (PR, branch, or commit), you analyze what changed and write a unit test covering the new behavior.

This skill writes unit tests only. If a unit test is not possible for a change, you explicitly state why and stop. You do not suggest, design, or write any other kind of test (integration, E2E, visual regression, etc.). Those belong to a separate flow.

Inputs

$ARGUMENTS — determines the source of the change. One of:

  • PR number: 42 or #42
  • Branch name: fix/user-auth
  • Commit SHA: abc1234
  • Empty — uses the current branch diff against the default branch

Step 1: Get the Diff

Understand the difference between the provided input and its source:

  • PR number — diff the PR branch against its base
  • Branch name — diff the branch against the default branch
  • Commit SHA — diff the commit against its parent
  • Empty — diff the current branch against the default branch

If no changes are detected, output: No changes detected. Provide a PR number, branch name, or commit SHA. and stop.

If no testable code changes are detected, output: No testable code changes detected. and stop.


Step 2: Understand the Project's Test Setup

Detect test framework — read package.json:

Framework Signals
Jest jest, @jest/globals, ts-jest, babel-jest in devDependencies
Vitest vitest, @vitest/ui in devDependencies
Bun test "test": "bun test" in scripts, no jest/vitest present
Mocha mocha, @types/mocha in devDependencies
Node test runner node --test in scripts

If no framework found, ask: "No test framework found. Which should we use?" (options: Jest / Vitest / Bun test).

Detect conventions — Glob: **/*.spec.ts, **/*.test.ts, **/*.spec.js, **/*.test.js, **/__tests__/**/*.ts. From 3–5 matches note: location pattern (co-located / __tests__ / tests/), naming, import style (path aliases), describe/it structure, mock patterns.


Step 3: Analyze the Changed Code

For each changed unit, summarize what changed and why in one sentence.


Step 4: Assess Unit Testability

This skill writes unit tests only. For each changed unit, determine whether a unit test suite is possible.

If the changed unit can be tested in isolation (pure logic, injectable deps, mockable boundaries) — proceed to Step 5.

❌ UNIT TEST NOT POSSIBLE — explain and stop

If the changed unit cannot be covered by a unit test suite, output the following and stop:

## Unit Test Not Possible

**Changed unit:** `file.ts:functionName`
**Change:** [one-sentence summary]

**A unit test suite cannot be written for this change because:** [specific reason — e.g., migration with no isolated logic, direct DB/HTTP call with no injection point, framework lifecycle wiring with no substitutable boundary]

Do not suggest alternative test types. Simply state that a unit test is not possible and why.

If ALL changed units are ❌, stop here. Do not proceed.


Step 5: Design the Test

For each ✅ unit, design the test before writing it.

5a: Find or determine the test file path

Check if a test file already exists for the changed file. If so, add to it. If not, determine the correct path from the project's conventions and create a new file.

5b: Design the test suite

Write a suite of unit tests that meaningfully covers the use cases introduced by the change.

5c: Test quality rules

  • Hardcoded expected values only. Never compute the expected value using the same logic as the production code. Pre-calculate it (e.g. via node -e or bun -e) and use a literal in the assertion.
  • Use it.each for parameterized tests. When multiple test cases share the same structure with different inputs/outputs, use it.each instead of copy-pasting test bodies.

Step 6: Choose Destination

Show a brief summary for each designed unit test:

## Unit Test to Write

**File:** `src/services/user.service.spec.ts` [NEW / EXISTING]
**Framework:** Jest
**Unit under test:** `UserService.validateEmail()`
**Fix covered:** "Ensures emails with + characters are accepted"

Then ask destination once (for all unit tests together):

AskUserQuestion: "Where should the unit test(s) be written?" with options:

Option Description
Locally (Recommended) Write directly to the working tree on the current branch
New branch + PR Create a new branch off the current branch, write there, push and open a PR targeting the current branch

If New branch + PR: see Branch + PR Creation section below before proceeding to Step 7.


Step 7: Write the Test

Add the test to the existing test file or create a new one following project conventions.

Verify TDD: check that the test fails against the source (before the change), then passes after it. If the test passes on both sides, it does not cover the change — revise it.

If the test fails to pass after the change, fix it and re-run once. If still failing, report the error and leave the file as-is.

Verify zero TS/lint errors. After the test passes at runtime, check IDE diagnostics or run the TS compiler to confirm zero type errors. Fix all errors before declaring done.


Branch + PR Creation

1. Detect current branch:

CURRENT_BRANCH=$(git branch --show-current)

2. Derive test branch name from the fix source:

Source Branch name
PR #42 test/pr-42
Branch fix/user-auth test/fix-user-auth
Commit abc1234 test/abc1234
Current branch feature/foo test/feature-foo

3. Create the branch and switch:

git checkout -b <test-branch-name>

4. Write all confirmed test files (proceed with Step 7 write logic).

5. Commit and push:

git add <test-file-1> [test-file-2 ...]
git commit -m "test: add unit tests for <fix summary>"
git push -u origin <test-branch-name>

Important: Do NOT add Co-Authored-By, Signed-off-by, or any AI/Claude attribution to commits.

6. Open PR targeting the original branch:

gh pr create \
  --base "$CURRENT_BRANCH" \
  --title "test: <fix summary>" \
  --body "$(cat <<'EOF'
## Unit tests

Adds unit tests for: **<fix summary>**

Tests added:
- <list each test file and what it covers>

These tests would have failed before the change was applied.
EOF
)"

Output the PR URL when done.

7. Apply label:

gh label create "unit-tests" --description "Unit tests PR" --color "0075ca" --force
gh pr edit $NEW_PR_NUMBER --add-label "unit-tests"

Output Format

## Unit Test: [PR title / branch / commit summary]

**Change:** [one-sentence summary]
**Source:** [PR #N / branch name / commit SHA]

---

### `src/services/user.service.ts` — `validateEmail()`

**Assessment:** ✅ Unit testable — pure logic, no external dependencies

**Test covers:** Email + character acceptance

**Test file:** `src/services/user.service.spec.ts` [created / updated]

**Test result:** ✅ Passed

---

### `src/db/migrations/0042_add_user_index.ts` — migration

**Assessment:** ❌ Unit test not possible
**Reason:** Database migration — no isolated logic to test.

---

## Summary

| Unit | Assessment | Test file | Result |
|---|---|---|---|
| `UserService.validateEmail` | ✅ Written | `user.service.spec.ts` | ✅ Pass |
| `0042_add_user_index` | ❌ Not possible | — | N/A |

Important

  • Unit tests only — if a unit test suite is not possible, state why and stop; do not suggest other test types