johnlindquist / ts-testing

Design, implement, and maintain high‑value TypeScript test suites using popular JS/TS testing libraries. Use this skill whenever the user is adding tests, debugging failing tests, or refactoring code that should be covered by tests.

1 views
0 installs

Skill Content

---
name: ts-testing
description: Design, implement, and maintain high‑value TypeScript test suites using popular JS/TS testing libraries. Use this skill whenever the user is adding tests, debugging failing tests, or refactoring code that should be covered by tests.
license: Complete terms in LICENSE.txt
---

This skill guides you to build pragmatic, maintainable test suites for TypeScript code. Focus on behavioral coverage, fast feedback, and alignment with the project's existing tooling.

The user is a TypeScript‑focused developer. They likely care about correctness, refactoring safety, and not drowning in flaky or brittle tests.

## When to use this skill

Use this skill when:

- The user is adding or updating unit, integration, or end‑to‑end tests
- The user reports a bug and wants a regression test
- The user is refactoring and wants confidence they didn’t break behavior
- The repo has test tooling configured (or clearly needs one) and you’re asked to “add tests” or “improve tests”

Do **not** invent a new test stack if the repo already has one. First detect and follow the existing setup.

## Library preferences

Always align with the repo first (check `package.json`, `devDependencies`, config files):

- If the repo already uses a framework (Jest, Vitest, Playwright, Cypress, etc.), **stick with it**.
- Only suggest new libraries if there is no obvious testing stack yet.

When you **must choose**, prefer:

- **Unit / integration tests**
  - Node / backend / shared libraries:  
    - Prefer **Vitest** (`vitest`) or **Jest** (`jest`)  
    - If `vitest` is present, use it. Else if `jest` is present, use that.
- **React / UI component tests**
  - Use **Testing Library** with the existing runner:
    - `@testing-library/react` with Vitest or Jest
- **End‑to‑end browser tests**
  - Prefer **Playwright** if installed or if starting from scratch
  - Use **Cypress** if the repo already uses it or the user asks for it explicitly

If the repo uses a less common stack (Mocha, Ava, Node’s built‑in test runner), respect that choice and adapt.

## Core testing philosophy

Follow these principles:

- **Test behavior, not implementation details**
  - For React/UI: test what the user sees and does (DOM, events, ARIA), not internal state or private methods
  - For services: test public APIs, not private helpers
- **Keep tests fast and focused**
  - Prefer small, deterministic tests that run quickly
  - Avoid unnecessary network, filesystem, or database calls unless you are explicitly writing integration tests
- **Make failures obvious**
  - Clear naming and assertions that explain *why* a test failed
  - Use descriptive test names following “given/when/then” style where helpful
- **Minimize mocking, but use it where it makes sense**
  - Mock external services, network calls, and slow dependencies
  - Avoid mocking your own business logic unless there’s a strong reason

## Standard workflow

When asked to add or improve tests, follow this workflow:

1. **Detect the existing stack**
   - Inspect `package.json` for `jest`, `vitest`, `@playwright/test`, `cypress`, `@testing-library/*`
   - Look for config files: `jest.config.*`, `vitest.config.*`, `playwright.config.*`, `cypress.config.*`
   - Check `test`, `unit`, or `e2e` scripts in `package.json`

2. **Locate the right place for the test**
   - Mirror existing patterns:
     - If tests live in `__tests__` directories, follow that
     - If they use `*.test.ts` or `*.spec.ts`, do the same
   - For UI: place tests near the component (e.g. `Component.test.tsx`) if that’s the existing convention

3. **Write the test in a TS‑friendly way**
   - Use `.test.ts` / `.test.tsx` (or `.spec`) as per repo convention
   - Avoid `any` in tests when possible; use real types or minimal interfaces to keep tests robust
   - For async code: use `await` with async test functions, avoid dangling promises

4. **Follow library‑specific best practices**

   **Vitest / Jest**
   - Use `describe` / `it` or `test` with clear names
   - Prefer `vi.fn()` / `jest.fn()` for spies and mocks
   - For modules: use `vi.mock()` / `jest.mock()` and keep mocks at the top of the file
   - For timers: use fake timers only when necessary (`vi.useFakeTimers()` / `jest.useFakeTimers()`)

   **React Testing Library**
   - Use `render`, `screen`, and user interactions (`userEvent`)
   - Query by role, label, text as a user would (prefer `getByRole`, `getByLabelText`)
   - Avoid querying by test IDs unless there’s no good semantic alternative

   **Playwright / Cypress**
   - Use existing fixtures and helpers (e.g. authenticated sessions, base URL) instead of re‑inventing them
   - Keep tests independent; don’t rely on order
   - Use `data-testid` or semantics consistently as locators

5. **Add regression tests for reported bugs**
   - Reproduce the bug in a failing test first
   - Only then change the implementation to make the test pass
   - Name regression tests clearly (e.g. `it("does not crash when X is null (regression #123)")`)

6. **Running tests**
   - Use existing scripts, e.g. `npm test`, `pnpm test`, `npx vitest`, `npx jest`, `npx playwright test`
   - If adding a new test command, wire it into `package.json` scripts following existing style

## Patterns to prefer

- **One behavior per test**: Don’t cram multiple unrelated assertions into a single test unless they’re part of the same scenario.
- **Helper factories**: Use small factory functions for building test data (`makeUser`, `makeOrder`) instead of duplicating setup.
- **Explicit async handling**: Always `await` promises; avoid passing async callbacks to APIs that don’t expect them.

## Anti‑patterns to avoid

- Overuse of snapshots for complex objects or DOM – use targeted assertions instead
- Testing private methods directly
- Heavy mocking that makes tests mirror implementation wiring
- Flaky tests that depend on real time, network, or global state without control

## TypeScript‑specific guidance

- Use the project’s `tsconfig` for tests when possible (`tsconfig.test.json` if present)
- Avoid silencing type errors just to “get tests compiling”
- When stubbing data, create minimal typed helpers rather than using `as any`

If the user asks you to generate tests, prefer **fewer, high‑value tests** that mirror real usage over large, mechanical test suites.