Skip to content

Testing Guide

This project uses Vitest with React Testing Library for comprehensive unit testing.

Testing Stack

Running Tests

pnpm test             # Watch mode
pnpm test:run         # Run once
pnpm test:watch       # Watch mode
pnpm test:coverage    # Coverage report

Test Configuration

Vitest Config (vitest.config.ts)

  • Environment: jsdom
  • Setup Files: Global mocks and utilities in src/__tests__/setup.ts
  • Coverage: v8 provider
  • Path Aliases: Same as production (@/src/)

Global Setup (src/__tests__/setup.ts)

Automatically mocks:

  • Next.js Router - Navigation hooks
  • Next.js Image - Renders as <img>
  • Framer Motion - Renders as plain elements
  • Browser APIs - IntersectionObserver, ResizeObserver, matchMedia
  • fetch - Global mock
  • localStorage - In-memory mock

Custom Render (src/__tests__/utils.tsx)

Wraps components in ThemeProvider:

import { render } from "@/__tests__/utils";
render(<MyComponent />); // Wrapped in ThemeProvider

Test Structure

src/__tests__/
├── setup.ts           # Global mocks
├── utils.tsx          # Custom render
├── lib/               # Utility tests
├── hooks/             # Hook tests
├── components/        # Component tests
└── app/api/og/        # OG API tests

Coverage Goals

  • Statements: > 80%
  • Branches: > 80%
  • Functions: > 80%
  • Lines: > 80%

Current coverage: 93% statements, 81% branches, 98% functions, 96% lines.

Common Patterns

Component Test

import { render, screen } from "@/__tests__/utils";
import MyComponent from "@/components/MyComponent";

it("renders", () => {
  render(<MyComponent />);
  expect(screen.getByText("Hello")).toBeDefined();
});

Hook Test

import { renderHook, waitFor } from "@testing-library/react";
import { useMyHook } from "@/hooks/use-my-hook";

it("fetches data", async () => {
  vi.mocked(global.fetch).mockResolvedValueOnce({
    ok: true,
    json: () => Promise.resolve({ data: [] }),
  } as Response);
  const { result } = renderHook(() => useMyHook());
  await waitFor(() => expect(result.current.loading).toBe(false));
});

Resources