Aller au contenu

React testing strategy

Stack

  • Runner — Jest 30 with ts-jest (ESM preset).
  • DOMjsdom enabled per-file via the @jest-environment jsdom docblock. The global default stays node so existing server-side tests keep their lightweight environment.
  • Library@testing-library/react for rendering and queries, @testing-library/user-event for interactions, @testing-library/jest-dom for extended matchers (registered via setupFilesAfterEnv).

Conventions

  1. ColocationFoo.test.tsx lives next to Foo.tsx. The test/ folder at the repo root is reserved for shared helpers.
  2. Queries — prefer getByRolegetByLabelTextgetByText. getByTestId is the last resort.
  3. User interactions — always userEvent, never fireEvent (user-event simulates the full event sequence).
  4. No snapshots — they drift silently. Assert the specific behaviour you care about.
  5. No type tests — TypeScript handles that.
  6. AAA — Arrange → Act → Assert. One behaviour per it.

Providers

Components that rely on i18n or the MUI theme must be rendered through renderWithProviders from @test/renderWithProviders. The test i18n instance is a pass-through: t('foo.bar') === 'foo.bar', which makes assertions deterministic without loading real translation files.

import { screen } from '@testing-library/react'
import { renderWithProviders } from '@test/renderWithProviders'
import { CoreButton } from './CoreButton'

renderWithProviders(<CoreButton text='save' />)
expect(screen.getByRole('button', { name: 'save' })).toBeInTheDocument()

Hooks

Use renderHook + act from @testing-library/react. If the hook depends on a provider, pass a wrapper:

const { result } = renderHook(() => useLocalStorage('k', 0))
act(() => result.current[1](1))
expect(result.current[0]).toBe(1)

Coverage

Coverage is collected automatically by npm test (v8 provider, reports: text, lcov, cobertura). Barrels (index.ts) and type-only files are excluded.

Raise coverageThreshold in jest.config.ts as modules gain tests. Suggested targets:

  • common/ utilities: 80 % lines / 70 % branches
  • client/hooks/: 80 % lines
  • client/components/: 60 % lines (pixel-level tests are not worth the churn)

Reference tests

See @platform/core for working examples:

  • modules/platform/core/src/client/hooks/useLocalStorage.test.ts
  • modules/platform/core/src/client/hooks/useT.test.tsx
  • modules/platform/core/src/client/components/CoreButton.test.tsx
  • modules/platform/core/src/client/components/Typo.test.tsx