OneApp Docs
Guides

Testing & QA

Testing ensures production reliability and catches bugs before users do — providing systematic strategies for unit tests, integration tests, component tests, E2E tests, and performance testing across all OneApp packages.

Why testing matters

Without systematic testing, codebases become fragile:

  • Production bugs — Critical errors reach users, damage reputation and revenue
  • Regression nightmares — Old bugs reappear after new features ship
  • Deployment anxiety — Fear of deploying because tests don't exist
  • Debugging chaos — Spend hours tracking down bugs that tests would catch instantly
  • Code brittleness — Can't refactor safely without breaking functionality
  • Documentation gaps — Tests serve as living documentation of expected behavior

OneApp's testing strategy provides comprehensive coverage across 6 layers — unit tests (Vitest for functions and utilities), component tests (React Testing Library for UI), integration tests (API routes with mocked dependencies), E2E tests (Playwright for critical user flows), performance tests (Lighthouse CI for Web Vitals), and accessibility tests (jest-axe for WCAG compliance) — with proven patterns from 40+ production applications.

Production-ready with Vitest 4 for blazing-fast tests, @testing-library/react for user-centric component testing, Playwright for reliable E2E automation, automated CI/CD pipeline integration, coverage thresholds (80% statements, 75% branches), pre-configured test utilities and helpers, and real-world examples from production codebases.

Use cases

Use testing to:

  • Prevent regressions — Catch bugs immediately when code changes break existing functionality
  • Document behavior — Tests serve as executable documentation of how code should work
  • Enable refactoring — Confidently restructure code knowing tests verify correctness
  • Speed up development — Find bugs in seconds, not hours of manual testing
  • Improve code quality — Writing testable code leads to better architecture
  • Reduce debugging time — Tests pinpoint exact failure location and cause

Quick Start

1. Write your first test

Start with a simple unit test:

// components/Button.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Button } from "./Button";

describe("Button", () => {
  it("renders with text", () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText("Click me")).toBeInTheDocument();
  });

  it("calls onClick handler", async () => {
    const handleClick = vi.fn();
    render(<Button onClick={handleClick}>Click</Button>);

    await userEvent.click(screen.getByText("Click"));
    expect(handleClick).toHaveBeenCalled();
  });
});

That's it! You've written your first test.

2. Run tests

Execute tests with Vitest:

# Run all tests
pnpm vitest

# Run tests in watch mode
pnpm vitest --watch

# Run with coverage
pnpm vitest --coverage

That's it! Tests run and show results.

3. Add tests to CI

Integrate with GitHub Actions:

# .github/workflows/test.yml
name: Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
      - run: pnpm install
      - run: pnpm vitest --run

That's it! Tests run automatically on every PR.

Complete testing reference

All testing categories

Unit & Function Testing

Test TypeToolPurpose
Unit Tests →VitestTest functions, utilities, pure logic
Hooks Testing →Vitest + renderHookTest React hooks in isolation
Server Actions →Vitest + mocksTest Next.js server actions

Use when: Testing utilities, hooks, business logic, pure functions

Component & UI Testing

Test TypeToolPurpose
Component Tests →@testing-library/reactTest React components with user interactions
Provider Context →Testing Library + providersTest components with Auth/Analytics context
Analytics Testing →Mocked analyticsVerify event tracking works correctly

Use when: Testing UI components, user interactions, context providers

Integration Testing

Test TypeToolPurpose
API Routes →Vitest + node-mocks-httpTest Next.js API endpoints
Database Operations →Vitest + test databaseTest Prisma queries and mutations

Use when: Testing API endpoints, database operations, service integration

E2E & Browser Testing

Test TypeToolPurpose
Playwright E2E →PlaywrightTest complete user flows across pages
Authentication Flow →PlaywrightTest login, signup, logout flows
Checkout Flow →PlaywrightTest critical business processes

Use when: Testing user workflows, critical paths, cross-page interactions

Performance & Quality

Test TypeToolPurpose
Performance Tests →Lighthouse CIMeasure Web Vitals and page speed
Accessibility Tests →jest-axeEnsure WCAG compliance

Use when: Optimizing performance, ensuring accessibility compliance

Unit Testing

Basic Setup

// components/Button.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Button } from "./Button";

describe("Button", () => {
  it("renders with text", () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText("Click me")).toBeInTheDocument();
  });

  it("calls onClick handler", async () => {
    const handleClick = vi.fn();
    render(<Button onClick={handleClick}>Click</Button>);

    await userEvent.click(screen.getByText("Click"));
    expect(handleClick).toHaveBeenCalled();
  });

  it("disables when disabled prop is true", () => {
    render(<Button disabled>Click</Button>);
    expect(screen.getByText("Click")).toBeDisabled();
  });
});

Testing Hooks

// hooks/useAuth.test.ts
import { renderHook, waitFor } from "@testing-library/react";
import { useAuth } from "@repo/auth/client/next";
import * as authModule from "@repo/auth/client/next";

describe("useAuth", () => {
  it("returns session when user is authenticated", async () => {
    // Mock the auth module
    vi.spyOn(authModule, "useAuth").mockReturnValue({
      session: {
        user: { id: "1", email: "test@example.com" }
      },
      isLoading: false
    });

    const { result } = renderHook(() => useAuth());

    await waitFor(() => {
      expect(result.current.session).toBeDefined();
      expect(result.current.session?.user.email).toBe("test@example.com");
    });
  });

  it("returns null session when not authenticated", async () => {
    vi.spyOn(authModule, "useAuth").mockReturnValue({
      session: null,
      isLoading: false
    });

    const { result } = renderHook(() => useAuth());

    await waitFor(() => {
      expect(result.current.session).toBeNull();
    });
  });
});

Testing Server Actions

// app/actions/user.ts
"use server";

import { auth } from "@repo/auth/server/next";

export async function updateUserName(name: string) {
  const session = await auth.api.getSession();
  if (!session) throw new Error("Not authenticated");

  // Update user...
  return { success: true, name };
}

// __tests__/actions/user.test.ts
import { updateUserName } from "#/app/actions/user";

describe("updateUserName", () => {
  it("updates user when authenticated", async () => {
    // Mock auth
    vi.mock("@repo/auth/server/next", () => ({
      auth: {
        api: {
          getSession: vi.fn().mockResolvedValue({
            user: { id: "1", name: "Old Name" }
          })
        }
      }
    }));

    const result = await updateUserName("New Name");
    expect(result.success).toBe(true);
    expect(result.name).toBe("New Name");
  });

  it("throws when not authenticated", async () => {
    vi.mock("@repo/auth/server/next", () => ({
      auth: {
        api: {
          getSession: vi.fn().mockResolvedValue(null)
        }
      }
    }));

    await expect(updateUserName("New Name")).rejects.toThrow("Not authenticated");
  });
});

Integration Testing

Testing API Routes

// app/api/users/route.ts
import { auth } from "@repo/auth/server/next";
import { db } from "@repo/db-prisma";

export async function GET(req: Request) {
  const session = await auth.api.getSession();
  if (!session) return new Response("Unauthorized", { status: 401 });

  const users = await db.user.findMany();
  return Response.json(users);
}

// __tests__/api/users.test.ts
import { GET } from "#/app/api/users/route";

describe("GET /api/users", () => {
  it("returns users when authenticated", async () => {
    // Mock session
    vi.mock("@repo/auth/server/next", () => ({
      auth: {
        api: {
          getSession: vi.fn().mockResolvedValue({
            user: { id: "1", email: "test@example.com" }
          })
        }
      }
    }));

    // Mock database
    vi.mock("@repo/db-prisma", () => ({
      db: {
        user: {
          findMany: vi.fn().mockResolvedValue([
            { id: "1", name: "John" },
            { id: "2", name: "Jane" }
          ])
        }
      }
    }));

    const response = await GET(new Request("http://localhost:3000"));
    const data = await response.json();

    expect(response.status).toBe(200);
    expect(data).toHaveLength(2);
  });

  it("returns 401 when not authenticated", async () => {
    vi.mock("@repo/auth/server/next", () => ({
      auth: {
        api: {
          getSession: vi.fn().mockResolvedValue(null)
        }
      }
    }));

    const response = await GET(new Request("http://localhost:3000"));
    expect(response.status).toBe(401);
  });
});

Testing Database Operations

// __tests__/database/user.test.ts
import { db } from "@repo/db-prisma";

describe("User Database", () => {
  beforeEach(async () => {
    // Clear test database before each test
    await db.user.deleteMany({});
  });

  it("creates a user", async () => {
    const user = await db.user.create({
      data: {
        email: "test@example.com",
        name: "Test User"
      }
    });

    expect(user.id).toBeDefined();
    expect(user.email).toBe("test@example.com");
  });

  it("finds user by email", async () => {
    await db.user.create({
      data: {
        email: "test@example.com",
        name: "Test User"
      }
    });

    const found = await db.user.findUnique({
      where: { email: "test@example.com" }
    });

    expect(found).toBeDefined();
    expect(found?.name).toBe("Test User");
  });

  it("updates user", async () => {
    const user = await db.user.create({
      data: {
        email: "test@example.com",
        name: "Old Name"
      }
    });

    const updated = await db.user.update({
      where: { id: user.id },
      data: { name: "New Name" }
    });

    expect(updated.name).toBe("New Name");
  });

  it("enforces unique email", async () => {
    await db.user.create({
      data: {
        email: "test@example.com",
        name: "User 1"
      }
    });

    await expect(
      db.user.create({
        data: {
          email: "test@example.com",
          name: "User 2"
        }
      })
    ).rejects.toThrow();
  });
});

Component Testing

Testing with Provider Context

// __tests__/components/Dashboard.test.tsx
import { render, screen } from "@testing-library/react";
import { Dashboard } from "#/components/Dashboard";
import { AuthProvider } from "@repo/auth/client/next";
import { AnalyticsProvider } from "@repo/analytics/client/next";

const renderWithProviders = (component: React.ReactNode) => {
  return render(
    <AuthProvider>
      <AnalyticsProvider config={{}}>{component}</AnalyticsProvider>
    </AuthProvider>
  );
};

describe("Dashboard", () => {
  it("displays user name from auth context", () => {
    // Mock useAuth
    vi.mock("@repo/auth/client/next", () => ({
      useAuth: () => ({
        session: {
          user: { id: "1", name: "John Doe" }
        }
      })
    }));

    renderWithProviders(<Dashboard />);
    expect(screen.getByText("Welcome John Doe")).toBeInTheDocument();
  });
});

Testing Analytics

// __tests__/components/EventTracker.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { EventTracker } from "#/components/EventTracker";
import { useAnalytics, track } from "@repo/analytics/client/next";

vi.mock("@repo/analytics/client/next", () => ({
  useAnalytics: vi.fn(),
  track: vi.fn()
}));

describe("EventTracker", () => {
  it("tracks button click event", async () => {
    const mockAnalytics = {
      emit: vi.fn()
    };
    (useAnalytics as any).mockReturnValue(mockAnalytics);

    render(<EventTracker />);
    await userEvent.click(screen.getByText("Track Event"));

    expect(mockAnalytics.emit).toHaveBeenCalled();
  });
});

End-to-End Testing

Playwright Setup

// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./e2e",
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: "html",
  use: {
    baseURL: "http://localhost:3000",
    trace: "on-first-retry"
  },

  projects: [
    {
      name: "chromium",
      use: { ...devices["Desktop Chrome"] }
    },
    {
      name: "firefox",
      use: { ...devices["Desktop Firefox"] }
    },
    {
      name: "webkit",
      use: { ...devices["Desktop Safari"] }
    }
  ],

  webServer: {
    command: "pnpm dev",
    url: "http://localhost:3000",
    reuseExistingServer: !process.env.CI
  }
});

E2E Test Examples

// e2e/auth.spec.ts
import { test, expect } from "@playwright/test";

test.describe("Authentication", () => {
  test("user can sign up", async ({ page }) => {
    // Navigate to signup
    await page.goto("/auth/signup");
    expect(await page.title()).toContain("Sign Up");

    // Fill form
    await page.fill('input[name="email"]', "newuser@example.com");
    await page.fill('input[name="password"]', "password123");
    await page.fill('input[name="name"]', "New User");

    // Submit
    await page.click("button:has-text('Sign Up')");

    // Verify redirect to dashboard
    await page.waitForURL("/dashboard");
    expect(await page.title()).toContain("Dashboard");
  });

  test("user can log in", async ({ page, context }) => {
    // Navigate to login
    await page.goto("/auth/login");

    // Fill credentials
    await page.fill('input[name="email"]', "test@example.com");
    await page.fill('input[name="password"]', "password123");

    // Submit
    await page.click("button:has-text('Sign In')");

    // Verify logged in
    await page.waitForURL("/dashboard");
    const authCookie = await context.cookies();
    expect(authCookie.some((c) => c.name === "session")).toBe(true);
  });

  test("user can log out", async ({ page }) => {
    // Login first
    await page.goto("/dashboard");

    // Find and click logout
    await page.click("button:has-text('Log Out')");

    // Verify redirected to login
    await page.waitForURL("/auth/login");
  });
});

// e2e/checkout.spec.ts
test.describe("Checkout Flow", () => {
  test("user can complete purchase", async ({ page }) => {
    // Login
    await page.goto("/auth/login");
    await page.fill('input[name="email"]', "test@example.com");
    await page.fill('input[name="password"]', "password123");
    await page.click("button:has-text('Sign In')");

    // Browse products
    await page.goto("/products");
    await page.click("button:has-text('Add to Cart'):first-of-type");

    // Go to checkout
    await page.goto("/checkout");
    expect(await page.textContent(".total")).toContain("$");

    // Fill payment (use test card)
    await page.fill('input[placeholder="Card number"]', "4242424242424242");
    await page.fill('input[placeholder="Expiry"]', "12/25");
    await page.fill('input[placeholder="CVC"]', "123");

    // Submit order
    await page.click("button:has-text('Place Order')");

    // Verify success
    await page.waitForURL("/orders/*");
    expect(await page.textContent("h1")).toContain("Order Confirmed");
  });
});

Performance Testing

Lighthouse CI

# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push, pull_request]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v10
        with:
          configPath: ./lighthouserc.json
          uploadArtifacts: true

Performance Benchmarks

// __tests__/performance.test.ts
import { performance } from "perf_hooks";

describe("Performance Benchmarks", () => {
  it("renders dashboard in < 100ms", async () => {
    const start = performance.now();
    render(<Dashboard />);
    const end = performance.now();

    expect(end - start).toBeLessThan(100);
  });

  it("loads users in < 500ms", async () => {
    const start = performance.now();
    const users = await fetchUsers();
    const end = performance.now();

    expect(end - start).toBeLessThan(500);
  });
});

Accessibility Testing

Testing Accessibility

// __tests__/accessibility.test.tsx
import { render } from "@testing-library/react";
import { axe, toHaveNoViolations } from "jest-axe";

expect.extend(toHaveNoViolations);

describe("Accessibility", () => {
  it("has no accessibility violations", async () => {
    const { container } = render(<Dashboard />);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });

  it("has proper ARIA labels", () => {
    const { getByRole } = render(<Button>Click me</Button>);
    const button = getByRole("button", { name: /click me/i });
    expect(button).toBeInTheDocument();
  });
});

Running Tests

Test Commands

CommandPurpose
pnpm vitestRun all tests
pnpm vitest --watchRun tests in watch mode
pnpm vitest --coverageRun tests with coverage report
pnpm vitest Button.test.tsxRun single test file
pnpm playwright testRun all E2E tests
pnpm playwright test auth.spec.tsRun specific E2E test
pnpm playwright test --debugDebug E2E tests with UI
pnpm playwright show-reportView Playwright test report

Coverage Goals

MetricTargetMinimumWhy It Matters
Statements80%70%Every line of code should be executed at least once
Branches75%60%All if/else paths should be tested
Functions80%70%Every function should have at least one test
Lines80%70%Overall code coverage for confidence

Testing Best Practices

✅ Test user behavior, not implementation

// ❌ Bad: Testing implementation details
test("calls updateUser function", () => {
  const spy = vi.spyOn(userService, "updateUser");
  // Tests internal function calls
});

// ✅ Good: Testing user behavior
test("updates user name when form is submitted", async () => {
  render(<UserForm />);
  await userEvent.type(screen.getByLabel("Name"), "New Name");
  await userEvent.click(screen.getByRole("button", { name: /save/i }));

  expect(screen.getByText("Name updated")).toBeInTheDocument();
});

✅ Use userEvent over fireEvent

// ❌ Bad: Using fireEvent
fireEvent.click(button);

// ✅ Good: Using userEvent (simulates real user interaction)
await userEvent.click(button);

✅ Always test error states

test("shows error message on failure", async () => {
  // Mock failed request
  vi.mock("fetch", () => ({
    default: vi.fn().mockRejectedValue(new Error("Failed"))
  }));

  render(<DataComponent />);
  await screen.findByText(/error/i);
});

✅ Clean up between tests

beforeEach(() => {
  // Clear mocks
  vi.clearAllMocks();
  // Clear database
  db.clear();
});

afterEach(() => {
  // Cleanup
  vi.restoreAllMocks();
});

Next steps

For Developers: Advanced testing techniques and CI/CD setup

Advanced Testing Techniques

Testing Stack Overview

ToolPurposeSetup
VitestUnit & integration testspnpm vitest
@testing-library/reactComponent testingPre-installed
@testing-library/user-eventUser interaction simulationPre-installed
PlaywrightE2E & browser testingpnpm add -D @playwright/test
jest-axeAccessibility testingpnpm add -D jest-axe
Lighthouse CIPerformance testingGitHub Action

Testing with Multiple Providers

// test-utils/renderWithProviders.tsx
import { render } from "@testing-library/react";
import { AuthProvider } from "@repo/auth/client/next";
import { AnalyticsProvider } from "@repo/analytics/client/next";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

export function renderWithProviders(component: React.ReactNode, { session = null, analyticsConfig = {} } = {}) {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false }
    }
  });

  return render(
    <QueryClientProvider client={queryClient}>
      <AuthProvider>
        <AnalyticsProvider config={analyticsConfig}>{component}</AnalyticsProvider>
      </AuthProvider>
    </QueryClientProvider>
  );
}

Snapshot Testing

// __tests__/components/Header.test.tsx
import { render } from "@testing-library/react";
import { Header } from "#/components/Header";

describe("Header", () => {
  it("matches snapshot", () => {
    const { container } = render(<Header />);
    expect(container).toMatchSnapshot();
  });
});

Visual Regression Testing

// e2e/visual.spec.ts
import { test, expect } from "@playwright/test";

test("homepage looks correct", async ({ page }) => {
  await page.goto("/");
  await expect(page).toHaveScreenshot("homepage.png");
});

test("dashboard looks correct", async ({ page }) => {
  // Login first
  await page.goto("/auth/login");
  await page.fill('input[name="email"]', "test@example.com");
  await page.fill('input[name="password"]', "password123");
  await page.click("button:has-text('Sign In')");

  // Take screenshot
  await page.goto("/dashboard");
  await expect(page).toHaveScreenshot("dashboard.png");
});

Load Testing

// __tests__/load/api.test.ts
import autocannon from "autocannon";

describe("Load Tests", () => {
  it("handles 100 requests/second", async () => {
    const result = await autocannon({
      url: "http://localhost:3000/api/users",
      connections: 10,
      duration: 10,
      headers: {
        Authorization: `Bearer ${testToken}`
      }
    });

    expect(result.errors).toBe(0);
    expect(result.requests.average).toBeGreaterThan(100);
  });
});

CI/CD Integration

GitHub Actions Complete Setup

# .github/workflows/test.yml
name: Tests

on:
  push:
    branches: [main, develop]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
        ports:
          - 5432:5432

    steps:
      - uses: actions/checkout@v3

      - uses: pnpm/action-setup@v2
        with:
          version: 10

      - uses: actions/setup-node@v3
        with:
          node-version: 22
          cache: "pnpm"

      - name: Install dependencies
        run: pnpm install

      - name: Setup test database
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
        run: pnpm prisma migrate deploy

      - name: Run unit tests
        run: pnpm vitest --run

      - name: Run integration tests
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
        run: pnpm vitest --run --config vitest.integration.config.ts

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/coverage-final.json

  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: pnpm/action-setup@v2

      - uses: actions/setup-node@v3
        with:
          node-version: 22
          cache: "pnpm"

      - name: Install dependencies
        run: pnpm install

      - name: Install Playwright browsers
        run: pnpm playwright install --with-deps

      - name: Run E2E tests
        run: pnpm playwright test

      - name: Upload Playwright report
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: playwright-report
          path: playwright-report/

  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: pnpm/action-setup@v2

      - uses: actions/setup-node@v3
        with:
          node-version: 22
          cache: "pnpm"

      - name: Install dependencies
        run: pnpm install

      - name: Build application
        run: pnpm build

      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v10
        with:
          configPath: ./lighthouserc.json
          uploadArtifacts: true

Pre-commit Testing

# .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# Run tests on staged files only
pnpm vitest related --run

# Run linting
pnpm lint-staged

Test Database Setup

# scripts/setup-test-db.sh
#!/bin/bash

# Create test database
psql -U postgres -c "CREATE DATABASE testdb;"

# Run migrations
DATABASE_URL="postgresql://test:test@localhost:5432/testdb" \
  pnpm prisma migrate deploy

# Seed test data
DATABASE_URL="postgresql://test:test@localhost:5432/testdb" \
  pnpm prisma db seed

Testing Patterns & Recipes

Testing AsyncResult Pattern

// __tests__/utils/asyncResult.test.ts
import { createUser } from "#/lib/users";

describe("createUser", () => {
  it("returns success with user data", async () => {
    const result = await createUser({
      email: "test@example.com",
      name: "Test User"
    });

    expect(result.success).toBe(true);
    if (result.success) {
      expect(result.data.email).toBe("test@example.com");
    }
  });

  it("returns error when email exists", async () => {
    // Create user first
    await createUser({
      email: "test@example.com",
      name: "Test User"
    });

    // Try to create again
    const result = await createUser({
      email: "test@example.com",
      name: "Another User"
    });

    expect(result.success).toBe(false);
    if (!result.success) {
      expect(result.error.message).toContain("already exists");
    }
  });
});

Testing Error Boundaries

// __tests__/components/ErrorBoundary.test.tsx
import { render, screen } from "@testing-library/react";
import { ErrorBoundary } from "#/components/ErrorBoundary";

function BrokenComponent() {
  throw new Error("Test error");
}

describe("ErrorBoundary", () => {
  it("catches errors and displays fallback", () => {
    // Suppress console.error for this test
    const consoleError = console.error;
    console.error = vi.fn();

    render(
      <ErrorBoundary fallback={<div>Something went wrong}>
        <BrokenComponent />
      </ErrorBoundary>
    );

    expect(screen.getByText("Something went wrong")).toBeInTheDocument();

    console.error = consoleError;
  });
});

Testing File Uploads

// __tests__/components/FileUpload.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { FileUpload } from "#/components/FileUpload";

describe("FileUpload", () => {
  it("uploads file successfully", async () => {
    // Mock uploadFile
    vi.mock("@repo/storage/client/next", () => ({
      uploadFile: vi.fn().mockResolvedValue({
        url: "https://cdn.example.com/file.jpg",
        size: 1024
      })
    }));

    render(<FileUpload />);

    // Create mock file
    const file = new File(["test content"], "test.jpg", {
      type: "image/jpeg"
    });

    // Upload file
    const input = screen.getByLabelText("Upload file");
    await userEvent.upload(input, file);

    // Verify success message
    expect(await screen.findByText(/uploaded successfully/i)).toBeInTheDocument();
  });
});

On this page