# Write Tests

> Write Tests for Existing Code

- Skill: `acedergren/write-tests` (Agent Skill)
- Install (CLI): `npx skillmds@latest add acedergren/write-tests`
- Raw SKILL.md: https://api.skillmd.com/api/skills/acedergren/write-tests/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: acedergren (https://skillmd.com/u/acedergren)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/acedergren/write-tests

---

# Write Tests for Existing Code

## When to Use

Load this skill when the user request matches the frontmatter description for Write Tests for Existing Code.

## NEVER

- **NEVER chain `mockResolvedValueOnce`** when `mockReset: true` — the chain clears between tests. Use counter-based `mockImplementation` instead.
- **NEVER define mock variables at module scope then reference them inside `vi.mock()` factories** — hoisting creates a temporal dead zone. Use `vi.hoisted()` or globalThis registry.
- **NEVER `vi.importActual()` for modules with side effects** — use selective re-exports instead.
- **NEVER test implementation details** (private state, internal call order) — test observable behavior through the public API.
- **NEVER copy mock patterns from other projects** — check YOUR test runner config first (`mockReset`, `mockClear`, `restoreMocks`).
- **NEVER modify source code** — this skill writes tests only; production behavior is fixed.

## Before Writing, Ask Yourself

- **Module type?** Each has a different mock strategy (see table below).
- **Blast radius?** Does this module have side effects (DB writes, API calls, filesystem) that need isolation?
- **Nearest test file?** Find the closest `*.test.ts` and match its exact mock structure — don't invent a new pattern.

## Mock Strategy by Module Type

| Module Type        | Strategy                                               |
| ------------------ | ------------------------------------------------------ |
| Route handler      | Test app builder + session simulation + `app.inject()` |
| Repository         | Mock DB connection + counter-based `execute`           |
| Framework plugin   | Real framework instance + selective dependency mocks   |
| Pure utility       | No mocks — test inputs/outputs directly                |
| Service w/ DI      | Mock injected deps via forwarding pattern              |

## Mock Setup (mockReset: true)

If your test runner uses `mockReset: true`, most examples from the internet will **silently fail** — return values clear between tests.

```typescript
const { mockFn } = vi.hoisted(() => ({
  mockFn: vi.fn(),
}));

vi.mock("./dependency", () => ({
  dependency: (...args: unknown[]) => mockFn(...args),
}));

beforeEach(() => {
  // MUST reconfigure here — mockReset clears return values between tests
  mockFn.mockResolvedValue(defaultResult);
});
```

For complex TDZ cases (multiple interdependent mocks), use the **globalThis registry pattern**:

```typescript
vi.mock("./dep", () => {
  if (!(globalThis as any).__mocks) (globalThis as any).__mocks = {};
  const m = { dep: vi.fn() };
  (globalThis as any).__mocks.dep = m;
  return { dep: (...a: unknown[]) => m.dep(...a) };
});
// In tests: const mocks = (globalThis as any).__mocks;
```

## Metacognitive Rule

**If >3 tests fail on first run**: STOP. Root cause is almost certainly a mock wiring issue affecting all tests — not individual test logic. Re-examine the mock setup holistically before fixing tests one by one.

## Run

```bash
npx vitest run <test-file> --reporter=verbose
```

## Arguments

- `$ARGUMENTS`: Path to the source file or module to cover
  - Example: `/write-tests src/routes/admin/settings.ts`
  - If empty: ask the user which file needs test coverage

