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.
Quick navigation
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 --coverageThat'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 --runThat's it! Tests run automatically on every PR.
Complete testing reference
All testing categories
Unit & Function Testing
| Test Type | Tool | Purpose |
|---|---|---|
| Unit Tests → | Vitest | Test functions, utilities, pure logic |
| Hooks Testing → | Vitest + renderHook | Test React hooks in isolation |
| Server Actions → | Vitest + mocks | Test Next.js server actions |
Use when: Testing utilities, hooks, business logic, pure functions
Component & UI Testing
| Test Type | Tool | Purpose |
|---|---|---|
| Component Tests → | @testing-library/react | Test React components with user interactions |
| Provider Context → | Testing Library + providers | Test components with Auth/Analytics context |
| Analytics Testing → | Mocked analytics | Verify event tracking works correctly |
Use when: Testing UI components, user interactions, context providers
Integration Testing
| Test Type | Tool | Purpose |
|---|---|---|
| API Routes → | Vitest + node-mocks-http | Test Next.js API endpoints |
| Database Operations → | Vitest + test database | Test Prisma queries and mutations |
Use when: Testing API endpoints, database operations, service integration
E2E & Browser Testing
| Test Type | Tool | Purpose |
|---|---|---|
| Playwright E2E → | Playwright | Test complete user flows across pages |
| Authentication Flow → | Playwright | Test login, signup, logout flows |
| Checkout Flow → | Playwright | Test critical business processes |
Use when: Testing user workflows, critical paths, cross-page interactions
Performance & Quality
| Test Type | Tool | Purpose |
|---|---|---|
| Performance Tests → | Lighthouse CI | Measure Web Vitals and page speed |
| Accessibility Tests → | jest-axe | Ensure 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: truePerformance 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
| Command | Purpose |
|---|---|
pnpm vitest | Run all tests |
pnpm vitest --watch | Run tests in watch mode |
pnpm vitest --coverage | Run tests with coverage report |
pnpm vitest Button.test.tsx | Run single test file |
pnpm playwright test | Run all E2E tests |
pnpm playwright test auth.spec.ts | Run specific E2E test |
pnpm playwright test --debug | Debug E2E tests with UI |
pnpm playwright show-report | View Playwright test report |
Coverage Goals
| Metric | Target | Minimum | Why It Matters |
|---|---|---|---|
| Statements | 80% | 70% | Every line of code should be executed at least once |
| Branches | 75% | 60% | All if/else paths should be tested |
| Functions | 80% | 70% | Every function should have at least one test |
| Lines | 80% | 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
- Learn error handling: Error Handling Guide →
- See architecture: Architecture Diagrams →
- Fix test issues: Troubleshooting →
For Developers: Advanced testing techniques and CI/CD setup
Advanced Testing Techniques
Testing Stack Overview
| Tool | Purpose | Setup |
|---|---|---|
| Vitest | Unit & integration tests | pnpm vitest |
| @testing-library/react | Component testing | Pre-installed |
| @testing-library/user-event | User interaction simulation | Pre-installed |
| Playwright | E2E & browser testing | pnpm add -D @playwright/test |
| jest-axe | Accessibility testing | pnpm add -D jest-axe |
| Lighthouse CI | Performance testing | GitHub 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: truePre-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-stagedTest 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 seedTesting 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();
});
});Related documentation
- Error Handling Guide → — AsyncResult pattern for testing
- Architecture Diagrams → — System flows to understand what to test
- Quick Reference → — Test command reference
- @repo/qa Package → — Shared test utilities and helpers
Troubleshooting Runbooks
Step-by-step diagnostic guides for solving common issues in OneApp applications — eliminate debugging time and resolve production problems fast.
AI-Assisted Development
Leverage AI assistants (Claude Code, GitHub Copilot, Cursor) with specialized agents and instruction files for faster, higher-quality development.