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:
ThemeContextbehavior 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.tsRationale:
- 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.tsx3) E2E tests: centralized
Rule: E2E tests MUST live under test/e2e/.
Structure:
test/
e2e/
onboarding.spec.ts
publish-post.spec.ts4) Prohibited patterns
- Do not duplicate a test at both
src/**andtest/**. - Do not create
test/contexts/**ortest/components/**mirrors ofsrc/**for unit tests.
File Naming Conventions
Test filenames
- Unit:
*.test.tsor*.test.tsx - Integration:
*.test.tsor*.test.tsx - E2E (framework-dependent):
- Playwright:
*.spec.ts - Cypress:
*.cy.ts
- Playwright:
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(orsrc/test/setup.ts) and reference it in the test runner config.
Shared test utilities
- Shared helpers MUST live in:
test/utils/for integration/E2E helperssrc/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.tsRule:
- 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.tsxExample: integration test
Location:
test/integration/theme-provider-flow.test.tsxExample: E2E test
Location:
test/e2e/onboarding.spec.tsMigration Guidance (if repo currently mixes patterns)
- Identify duplicated tests (same subject in
src/**andtest/**). - Keep co-located tests for units; delete/move centralized duplicates.
- Move cross-module tests into
test/integration/. - 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.