- Goals
- Tools
- Test Types
- Next.js-Specific Testing Rules
- Mocking Strategy
- React Strict Mode
- Directory Structure
- Running Tests
- Ensure reliability of client and server components.
- Prevent regressions in routing, rendering, and data‑fetching logic.
- Provide fast feedback during development using Vitest.
- Maintain confidence in critical user flows through integration and E2E testing.
- Keep tests deterministic, isolated, and easy to maintain.
- Vitest
- @testing-library/react
- @testing-library/jest-dom
- happy-dom or jsdom
- MSW (Mock Service Worker)
- Playwright (recommended) or Cypress
Used for: utility functions, server‑side logic, small client components. Keep tests isolated; mock external dependencies; avoid rendering full pages.
Used for: client components, server components (rendering only). Test behavior, not implementation details. Finding elements: Use Testing Library queries. Prefer, in order: getByRole, getByLabelText, getByText. Use getByTestId only when role/label/text are impractical; see Data attributes. Do not use document.querySelector or other DOM selectors. Mock server actions and data‑fetching.
Used for: API routes, server actions, data‑fetching logic, component + API interaction. Use MSW for network mocking; avoid real external API calls.
Used for: routing, authentication, critical user journeys. Keep E2E tests minimal and focused; only test critical paths; avoid flakiness.
- Test pure logic with unit tests
- Test rendering with component tests
- Mock
fetch, server actions, and external services
- Use React Testing Library
- Mock
next/navigationwhen needed
Example mock:
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: vi.fn(), replace: vi.fn(), back: vi.fn() }),
}));We use a hybrid approach:
- Unit and component tests (Vitest): Run without React Strict Mode. Do not wrap components in
<StrictMode>in custom render helpers or test setup; do not callconfigure({ reactStrictMode: true })in RTL. Prefer exact assertions (e.g.toHaveLength(n),getByRole(...)for a single element). If your environment still double-renders, use resilient assertions:expect(elements.length).toBeGreaterThanOrEqual(n)for counts andgetAllByRole/getAllByLabelTextthenexpect(...length).toBeGreaterThanOrEqual(1)for elements that should appear once. - E2E tests: Run the real app. The app uses whatever is configured in the Next.js root (e.g.
reactStrictModeinnext.config.js).
- Unit and component tests: Co-locate with source as
*.test.tsor*.test.tsxnext to the file under test (e.g.page.tsx→page.test.tsx,utils.ts→utils.test.ts). - Integration tests: Place in
tests/integration/at the project root (e.g.tests/integration/metadata-flow.test.tsx). Use MSW and real or wrapped providers only where the test needs a multi-module flow. - E2E tests: Place in
e2e/ortests/e2e/(e.g. Playwright specs). Keep them separate from Vitest so they can be run with a different command and config.
Configure scripts in package.json as needed, for example:
npm run testorvitestfor unit/component testsnpm run test:integrationfor integration tests (if configured)npm run test:e2efor E2E (Playwright/Cypress)