Skip to content

Repo-wide Testing Conventions

Purpose

This document defines where tests live, how they are named, what they are allowed to do, and how we run them. The goals are:

  • Make it obvious where to add a test.
  • Keep unit tests close to the code they protect.
  • Keep integration/E2E tests organized and runnable in CI.
  • Ensure consistent naming, tooling, and patterns across the repo.

Test Type Definitions

Unit tests

A unit test verifies a single module in isolation (a component, hook, context, util). It should not require external services and should minimize dependencies.

Examples:

  • ThemeContext behavior with a mocked storage API
  • A hook’s state transitions
  • A pure utility function

Integration tests

An integration test verifies multiple modules working together (two+ components, context + component, API client + data shaping). It may involve routing, multiple providers, or more realistic mocks.

Examples:

  • A page renders with providers and responds to user interactions
  • A form submission triggers API calls and updates UI state

End-to-end (E2E) tests

An E2E test verifies real user flows in a browser-like environment (often against a running app). It is the highest-level and slowest tier.

Examples:

  • Sign up → onboarding → connect account → publish post

Directory Layout

1) Unit tests (default): co-located with source

Rule: Unit tests MUST live next to the file they test.

Structure:

src/
  components/
    Button.tsx
    Button.test.tsx
  contexts/
    ThemeContext.tsx
    ThemeContext.test.tsx
  hooks/
    useFoo.ts
    useFoo.test.ts
  infra/
    formatDate.ts
    formatDate.test.ts

Rationale:

  • Simplest navigation
  • Tests move with code
  • Encourages consistent ownership

2) Integration tests: centralized

Rule: Integration tests MUST live under test/integration/.

Structure:

test/
  integration/
    theme-provider-flow.test.tsx
    onboarding-form.test.tsx

3) E2E tests: centralized

Rule: E2E tests MUST live under test/e2e/.

Structure:

test/
  e2e/
    onboarding.spec.ts
    publish-post.spec.ts

4) Prohibited patterns

  • Do not duplicate a test at both src/** and test/**.
  • Do not create test/contexts/** or test/components/** mirrors of src/** for unit tests.

File Naming Conventions

Test filenames

  • Unit: *.test.ts or *.test.tsx
  • Integration: *.test.ts or *.test.tsx
  • E2E (framework-dependent):
    • Playwright: *.spec.ts
    • Cypress: *.cy.ts

Rule: Prefer *.test.* for unit/integration and reserve the E2E suffix for the E2E framework.

Test names

  • Use descriptive describe() blocks that map to the unit under test.
  • Use test case names that describe behavior, not implementation.

Good:

  • it('uses system theme when no preference is set')

Avoid:

  • it('calls setState twice')

Testing Tools and Libraries

Unit/Integration runner

  • Preferred: Vitest (if using Vite tooling)
  • Alternative: Jest (if repo is already standardized on it)

DOM and component testing

  • @testing-library/react
  • @testing-library/user-event

Mocks

  • Prefer MSW for network-level mocking in integration tests.
  • Prefer lightweight module mocks for unit tests.

What Tests May and May Not Do

Unit tests

Allowed:

  • Mock fetch / API clients
  • Mock localStorage / sessionStorage
  • Render a single component with minimal providers

Not allowed:

  • Real network calls
  • Writing files
  • Depending on a running server

Integration tests

Allowed:

  • MSW request handlers
  • Multiple providers (router, theme, auth)
  • More realistic workflows

Not allowed:

  • Real external network calls (unless explicitly approved and isolated)

E2E tests

Allowed:

  • Running the app and testing real flows
  • Using seeded test data

Not allowed:

  • Hitting production services

Test Setup and Shared Utilities

Global test setup

  • Put global setup in test/setup.ts (or src/test/setup.ts) and reference it in the test runner config.

Shared test utilities

  • Shared helpers MUST live in:
    • test/utils/ for integration/E2E helpers
    • src/test-utils/ for helpers used by co-located unit tests

Recommended structure:

src/
  test-utils/
    renderWithProviders.tsx
    mockStorage.ts

test/
  utils/
    msw/
      server.ts
      handlers.ts

Rule:

  • Prefer local helpers in a co-located test file when only used once.
  • Promote to shared utilities only when used in 2+ places.

Coverage Expectations

  • Unit tests cover the critical logic paths in components/hooks/contexts.
  • Integration tests cover cross-module behavior and regression-prone flows.
  • E2E tests cover only the highest-value user journeys.

Guidance:

  • Prefer many fast unit tests.
  • Use fewer integration tests.
  • Use the fewest E2E tests.

Running Tests

Local development

  • Unit + Integration:

    • pnpm test
    • or npm run test
  • Watch mode:

    • pnpm test --watch

CI

  • Run unit/integration on every PR.
  • Run E2E on:
    • main branch merges
    • nightly schedule
    • or when a label is applied (team preference)

Linting and Formatting for Tests

  • Tests must pass the same lint rules as source.
  • Avoid disabling lint rules; prefer local suppressions with a comment when necessary.

Practical Examples

Example: context unit test

Location:

src/contexts/ThemeContext.test.tsx

Example: integration test

Location:

test/integration/theme-provider-flow.test.tsx

Example: E2E test

Location:

test/e2e/onboarding.spec.ts

Migration Guidance (if repo currently mixes patterns)

  1. Identify duplicated tests (same subject in src/** and test/**).
  2. Keep co-located tests for units; delete/move centralized duplicates.
  3. Move cross-module tests into test/integration/.
  4. Update test runner globs to include:
    • src/**/*.test.{ts,tsx}
    • test/integration/**/*.test.{ts,tsx}
    • E2E framework defaults under test/e2e/**

Decision Log

  • Unit tests are co-located with source to preserve locality and simplify refactors.
  • Central test/ folder is reserved for integration and E2E tests only.

TendSocial Documentation