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
在測試依賴 useRouter 或 useSearchParams 的組件時,我們需要 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.ts 和 tests 資料夾。
撰寫 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
測試策略建議
單元測試 (Vitest):
- 覆蓋所有 Utility Functions (
utils/). - 測試複雜的 UI 互動元件 (Client Components).
- Mock 掉外部依賴,專注於邏輯正確性.
- 覆蓋所有 Utility Functions (
E2E 測試 (Playwright):
- 覆蓋 "Happy Path" (最主要的使用者流程,如:註冊 -> 登入 -> 購買).
- 測試跨頁面的互動.
- 測試真實的 Server Components 渲染結果.
小結
- 使用 Vitest 進行快速的回饋循環測試。
- 使用 Playwright 確保使用者體驗在真實瀏覽器中正常運作。
- 善用
vi.mock與page.route來隔離外部依賴,提升測試穩定性。
建立完善的測試網是專案長期維護的關鍵,它能讓你更有信心地進行重構與功能升級。