Next.js 測試實戰 (Vitest & Playwright)

隨著應用程式規模擴大,手動測試變得緩慢且容易出錯。Next.js 支援多種測試工具,讓我們能針對不同層級進行自動化檢驗。本篇將專注於目前 Next.js 社群最推薦的組合:Vitest (單元測試) 與 Playwright (E2E 測試)。

單元與整合測試 (Vitest)

Vitest 是基於 Vite 的測試框架,它的最大優勢是速度快,且能與現代前端工具鏈完美整合。它相容於 Jest 的 API (describe, it, expect),因此學習曲線極低。

環境建置 (Setup)

安裝必要的套件:

# 安裝 Vitest, React Testing Library, jsdom
npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/user-event @testing-library/dom

在專案根目錄建立 vitest.config.ts

import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true, // 允許直接使用 describe, it, expect
    setupFiles: './vitest.setup.ts', // 初始化設定
    alias: {
      '@': resolve(__dirname, './src'), // 處理 Path Alias
    },
  },
});

建立 vitest.setup.ts 來擴充 Matchers (例如 toBeInTheDocument):

import '@testing-library/jest-dom';

測試 Client Components

Client Components 包含互動邏輯 (onClick, useEffect),我們使用 @testing-library/react 來模擬渲染與使用者操作。

// components/Counter.tsx
'use client';
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>Increment</button>
    </div>
  );
}
// __tests__/Counter.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from '@/components/Counter';

describe('Counter Component', () => {
  it('點擊按鈕後數字應該增加', async () => {
    const user = userEvent.setup();
    render(<Counter />);

    // 驗證初始狀態
    expect(screen.getByText('Count: 0')).toBeInTheDocument();

    // 模擬點擊
    const button = screen.getByRole('button', { name: /increment/i });
    await user.click(button);

    // 驗證結果
    expect(screen.getByText('Count: 1')).toBeInTheDocument();
  });
});

Mocking Next.js Hooks

在測試依賴 useRouteruseSearchParams 的組件時,我們需要 Mock 這些 Hook。可以使用 vi.mock

import { render, screen } from '@testing-library/react';
import { vi } from 'vitest';
import LoginButton from '@/components/LoginButton';

// Mock next/navigation
vi.mock('next/navigation', () => ({
  useRouter: () => ({
    push: vi.fn(),
  }),
  useSearchParams: () => ({
    get: vi.fn(() => 'redirect_url'),
  }),
}));

test('LoginButton 渲染', () => {
  render(<LoginButton />);
  // ...
});

端對端測試 (Playwright)

Playwright 是由微軟開發的自動化測試工具,它能開啟真實的瀏覽器 (Chromium, Firefox, WebKit),模擬使用者在網站上的所有操作。

初始化

npm init playwright@latest

這會產生 playwright.config.tstests 資料夾。

撰寫 E2E 測試

Playwright 的核心概念是 Locator,它比 CSS selector 更穩定且具語意化。

// tests/example.spec.ts
import { test, expect } from '@playwright/test';

test('首頁導航測試', async ({ page }) => {
  // 1. 前往首頁
  await page.goto('http://localhost:3000');

  // 2. 驗證標題
  await expect(page).toHaveTitle(/My App/);

  // 3. 找到連結並點擊
  await page.getByRole('link', { name: '關於我們' }).click();

  // 4. 驗證網址是否改變
  await expect(page).toHaveURL(/.*\/about/);

  // 5. 驗證頁面內容
  await expect(page.getByRole('heading', { name: 'About Us' })).toBeVisible();
});

攔截 API (Mocking API)

在 E2E 測試中,為了避免依賴不穩定的後端 API,我們可以使用 page.route 來攔截請求並回傳假資料。

test('產品列表顯示 (Mock API)', async ({ page }) => {
  // 攔截 API 請求
  await page.route('*/**/api/products', async (route) => {
    const json = [{ id: 1, name: 'Mock Product' }];
    await route.fulfill({ json });
  });

  await page.goto('/products');

  // 驗證畫面是否顯示 Mock 的資料
  await expect(page.getByText('Mock Product')).toBeVisible();
});

CI/CD 整合 (GitHub Actions)

要讓測試發揮最大價值,必須整合到 CI 流程中。以下是 GitHub Actions 的範例設定 .github/workflows/test.yml

name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 18

      - name: Install dependencies
        run: npm ci

      - name: Run Unit Tests
        run: npm run test

      - name: Install Playwright Browsers
        run: npx playwright install --with-deps

      - name: Run E2E Tests
        run: npx playwright test

測試策略建議

  1. 單元測試 (Vitest)

    • 覆蓋所有 Utility Functions (utils/).
    • 測試複雜的 UI 互動元件 (Client Components).
    • Mock 掉外部依賴,專注於邏輯正確性.
  2. E2E 測試 (Playwright)

    • 覆蓋 "Happy Path" (最主要的使用者流程,如:註冊 -> 登入 -> 購買).
    • 測試跨頁面的互動.
    • 測試真實的 Server Components 渲染結果.

小結

  • 使用 Vitest 進行快速的回饋循環測試。
  • 使用 Playwright 確保使用者體驗在真實瀏覽器中正常運作。
  • 善用 vi.mockpage.route 來隔離外部依賴,提升測試穩定性。

建立完善的測試網是專案長期維護的關鍵,它能讓你更有信心地進行重構與功能升級。