2.1 KiB
2.1 KiB
Test Guard — JavaScript / TypeScript / Jest / Vitest Patterns
Concrete applications of the nine rules for JS/TS projects. Read this when reviewing or writing Jest or Vitest tests.
Rule 2: Mock boundaries in JS/TS
Justified mock targets:
- Network: prefer
msw(Mock Service Worker) overjest.mock-ing your own fetch wrapper — it mocks at the true boundary - LLM / third-party SDK clients (
openai,@anthropic-ai/sdk, Stripe, etc.) - Timers and randomness:
vi.useFakeTimers()/jest.useFakeTimers(), seeded RNG - Filesystem and process env in Node code
Unjustified mocks:
jest.mock('../utils/helpers')— mocking your own internal module to isolate a "unit" (Rule 2)- Mocking a class's private method via prototype patching (Rule 1)
- Hand-built object literals pretending to be domain entities when a real constructor or factory exists (Rule 8)
Rule 3: test.each
// Violation: three near-identical it() blocks
// Fix:
test.each([
['Hello World', 'hello-world'],
[' padded ', 'padded'],
['Café Menu', 'cafe-menu'],
])('slugify(%s) → %s', (raw, expected) => {
expect(slugify(raw)).toBe(expected);
});
Snapshot discipline
Snapshot tests are implementation tests in disguise unless the snapshot is the contract (e.g., a public JSON output, a CLI's help text). Avoid snapshots of:
- Full component trees that change on every styling tweak (Rule 1 — brittle, asserts implementation)
- Large objects nobody reviews — an unread snapshot approves itself (Rule 4)
Prefer targeted assertions: expect(screen.getByRole('button')).toHaveTextContent('Save').
UI component tests
- Test what the user sees and does (Testing Library queries by role/label), not component internals or state hooks (Rule 1).
- Don't test that React renders, routes resolve, or props propagate — framework guarantees (Rule 7).
Rule 9: Real persistence
For data-layer logic (Prisma/Drizzle/Knex queries), run against a real test database — testcontainers, a Dockerized Postgres, or SQLite where compatible. Mocking the query builder to test the query builder tests nothing.