測試

範例

了解如何在 Next.js 中設定常用測試工具 CypressPlaywrightJest with React Testing Library

Cypress

Cypress 是用於 端到端(E2E)整合測試 的測試執行器。

快速開始

你可以將 create-next-appwith-cypress 範例 一起使用以快速入門。

npx create-next-app@latest --example with-cypress with-cypress-app

手動設定

為了開始使用 Cypress,請安裝 cypress 套件:

npm install --save-dev cypress

將 Cypress 加入到 package.json 中:

"scripts": { "dev": "next dev", "build": "next build", "start": "next start", "cypress": "cypress open", }

執行 Cypress 以生成與使用其建議的資料夾結構範例:

npm run cypress

你可以查看生成的範例和 Cypress 文件中的 撰寫你的第一個測試 章節,來幫助你熟悉 Cypress。

建立你的第一個 Cypress 整合測試

假設你有以下兩個 Next.js 頁面:

// pages/index.js import Link from 'next/link' export default function Home() { return ( <nav> <Link href="/about"> <a>About</a> </Link> </nav> ) }
// pages/about.js export default function About() { return ( <div> <h1>About Page</h1> </div> ) }

加入一個測試來檢查你的導航是否正常執行:

// cypress/integration/app.spec.js describe('Navigation', () => { it('should navigate to the about page', () => { // Start from the index page cy.visit('http://localhost:3000/') // Find a link with an href attribute containing "about" and click it cy.get('a[href*="about"]').click() // The new url should include "/about" cy.url().should('include', '/about') // The new page should contain an h1 with "About page" cy.get('h1').contains('About Page') }) })

如果你加入了 "baseUrl": "http://localhost:3000"cypress.json 組態檔案中,則可以直接使用較短的寫法 cy.visit("/"),而不用完整寫出 cy.visit("http://localhost:3000/")

執行 Cypress 測試

由於 Cypress 是用來測試真正的 Next.js 應用程式,所以執行測試前,我們需要先開啟一個 Next.js 伺服器。我們建議你針對生產環境來進行測試,以更貼近真實情境。

執行 npm run buildnpm run start,然後開啟另一個終端機來執行 npm run cypress 指令以啟動 Cypress。

注意: 或者,你可以安裝 start-server-and-test 套件和加入 "test": "start-server-and-test start http://localhost:3000 cypress"package.json 中,以啟動 Next.js 的生產環境伺服器,並結合 Cypress 使用。請記得在有任何新修改後,重新打包你的應用程式。

為持續整合(CI)做好準備

你會注意到,到目前為止執行 Cypress 會自動開啟一個瀏覽器,這對 CI 來說不好。如果不想讓他自動開啟,你可以用 cypress run 來以無頭(headless)模式執行 Cypress:

// package.json "scripts": { //... "cypress": "cypress open", "cypress:headless": "cypress run", "e2e": "start-server-and-test start http://localhost:3000 cypress", "e2e:headless": "start-server-and-test start http://localhost:3000 cypress:headless" }

你可以從以下資源中了解更多 Cypress 和 持續整合:

Playwright

Playwright 是一個測試框架,可讓你使用單個 API 自動化 Chromium、Firefox 和 WebKit。你可以使用它在所有平台上撰寫 端到端 (E2E)整合 測試。

快速開始

最快的入門方法是使用 create-next-appwith-playwright 範例。這將建立一個 Next.js 項目,其中包含設定好的 Playwright。

npx create-next-app@latest --example with-playwright with-playwright-app

手動設定

你也可以使用 npm init playwright 來加入 Playwright 到你現有的 NPM 專案。

要手動開始使用 Playwright,請安裝 @playwright/test 套件:

npm install --save-dev @playwright/test

加入 Playwright 到 package.json

"scripts": { "dev": "next dev", "build": "next build", "start": "next start", "test:e2e": "playwright test", }

建立你的第一個 Playwright 端到端測試

假設你有以下兩個 Next.js 頁面:

// pages/index.js import Link from 'next/link' export default function Home() { return ( <nav> <Link href="/about"> <a>About</a> </Link> </nav> ) }
// pages/about.js export default function About() { return ( <div> <h1>About Page</h1> </div> ) }

加入一個測試來檢查你的導航是否正常執行:

// e2e/example.spec.ts import { test, expect } from '@playwright/test' test('should navigate to the about page', async ({ page }) => { // Start from the index page (the baseURL is set via the webServer in the playwright.config.ts) await page.goto('http://localhost:3000/') // Find an element with the text 'About Page' and click on it await page.click('text=About') // The new url should be "/about" (baseURL is used there) await expect(page).toHaveURL('http://localhost:3000/about') // The new page should contain an h1 with "About Page" await expect(page.locator('h1')).toContainText('About Page') })

如果你加入了 "baseURL": "http://localhost:3000"playwright.config.ts 配置檔案中,則可以直接使用較短的寫法 page.goto("/"),而不用完整寫出 page.goto("http://localhost:3000/")

執行你的 Playwright 測試

由於 Playwright 是用來測試真正的 Next.js 應用程式,所以執行測試前,我們需要先開啟一個 Next.js 伺服器。我們建議你針對生產環境來進行測試,以更貼近真實情境。

執行 npm run buildnpm run start,然後開啟另一個終端機來執行 npm run test:e2e 指令以啟動 Playwright。

注意: 或者你可以使用 webServer 功能來讓 Playwright 啟動開發伺服器並等待它啟動完成。

在持續整合(CI)中執行 Playwright

Playwright 會預設以 無頭模式 來執行測試。要安裝所有 Playwright 依賴套件,請執行 npx playwright install-deps

你可以從以下資源中了解更多 Playwright 和持續整合:

Jest 和 React 測試庫

Jest 和 React 測試庫經常一起用於 單元測試。你可以透過三種方式在 Next.js 應用程式中開始使用 Jest:

  1. 使用我們的其中一個 快速入門範例
  2. 使用 Next.js Rust 編譯器
  3. 使用 Babel

以下將介紹如何設定 Jest:

快速開始

你可以將 create-next-appwith-jest 範例一起使用,以快速開始使用 Jest 和 React 測試庫:

npx create-next-app@latest --example with-jest with-jest-app

設定 Jest(使用 Rust 編譯器)

Next.js 12 發布以來,Next.js 現在內建 Jest 的配置項。

要設定 Jest,請安裝 jestjest-environment-jsdom@testing-library/react@testing-library/jest-dom

npm install --save-dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom

在專案的根目錄中建立一個 jest.config.js 檔案,並加入以下內容:

// jest.config.js const nextJest = require('next/jest') const createJestConfig = nextJest({ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment dir: './', }) // Add any custom config to be passed to Jest /** @type {import('jest').Config} */ const customJestConfig = { // Add more setup options before each test is run // setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work moduleDirectories: ['node_modules', '<rootDir>/'], testEnvironment: 'jest-environment-jsdom', } // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async module.exports = createJestConfig(customJestConfig)

深入研究 Next.js 後,會發現 next/jest 自動幫你配置了 Jest,包括:

  • 使用 SWC 設定 transform
  • 自動模擬樣式表(.css.module.css 及其 scss 變體)和圖片引入
  • .env(和所有變體)載入到 process.env
  • 從測試解析和轉​​換中忽略 node_modules
  • 從測試解析中忽略 .next
  • 加載 next.config.js,作為啟用 SWC 轉換的標記

注意: 為了直接測試環境變數,請在單獨的設定腳本或 jest.config.js 檔案中手動加載它們。請參閱 測試環境變數 章節。

設定 Jest(使用 Babel)

如果你在安裝時選擇手動排除 Rust 編譯器,除了上述套件外,你還需要手動配置 Jest 並安裝 babel-jestidentity-obj-proxy

以下是在 Next.js 配置 Jest 的建議選項:

// jest.config.js module.exports = { collectCoverage: true, // on node 14.x coverage provider v8 offers good speed and more or less good report coverageProvider: 'v8', collectCoverageFrom: [ '**/*.{js,jsx,ts,tsx}', '!**/*.d.ts', '!**/node_modules/**', '!<rootDir>/out/**', '!<rootDir>/.next/**', '!<rootDir>/*.config.js', '!<rootDir>/coverage/**', ], moduleNameMapper: { // Handle CSS imports (with CSS modules) // https://jestjs.io/docs/webpack#mocking-css-modules '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', // Handle CSS imports (without CSS modules) '^.+\\.(css|sass|scss)$': '<rootDir>/__mocks__/styleMock.js', // Handle image imports // https://jestjs.io/docs/webpack#handling-static-assets '^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)$/i': `<rootDir>/__mocks__/fileMock.js`, // Handle module aliases '^@/components/(.*)$': '<rootDir>/components/$1', }, // Add more setup options before each test is run // setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'], testEnvironment: 'jsdom', transform: { // Use babel-jest to transpile tests with the next/babel preset // https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }], }, transformIgnorePatterns: [ '/node_modules/', '^.+\\.module\\.(css|sass|scss)$', ], }

你可以在 Jest 文件 中了解有關每個配置選項的更多資訊。

處理樣式表和圖片引入

測試時並沒有包含樣式表和圖片,因此引入時可能會導致錯誤,因此我們需要模擬它們。在 __mocks__ 資料夾中建立上面配置引用的模擬文件 - fileMock.jsstyleMock.js

// __mocks__/fileMock.js module.exports = { src: '/img.jpg', height: 24, width: 24, blurDataURL: 'data:image/png;base64,imagedata', }
// __mocks__/styleMock.js module.exports = {}

有關處理靜態資源的更多資訊,請參閱 Jest Docs

可選:使用自訂匹配器擴展 Jest

@testing-library/jest-dom 包括一組方便的 自定義匹配器,例如 .toBeInTheDocument() 可以幫你確認指定的 DOM 有沒有渲染出來,使寫測試更簡單。你可以加入以下選項到 Jest 配置文件,來為每個測試引入自定義匹配器:

// jest.config.js setupFilesAfterEnv: ['<rootDir>/jest.setup.js']

然後,在 jest.setup.js 中,加入以下引入:

// jest.setup.js import '@testing-library/jest-dom/extend-expect'

如果你需要在每次測試之前加入更多設置選項,通常可以將它們加入到上面的 jest.setup.js 文件中。

可選:絕對匯入和模組路徑別名

如果你的專案中使用 模組路徑別名,你需要配置 Jest 來匹配 jsconfig.json 文件中的路徑選項與 jest.config.js 文件中的 moduleNameMapper 選項,以解析匯入。例如:

// tsconfig.json or jsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/components/*": ["components/*"] } } }
// jest.config.js moduleNameMapper: { '^@/components/(.*)$': '<rootDir>/components/$1', }

建立你的測試

將測試腳本加入到 package.json

將監視模式下的 Jest 可執行文件加入到 package.json 腳本中:

"scripts": { "dev": "next dev", "build": "next build", "start": "next start", "test": "jest --watch" }

jest --watch 用途為文件有任何修改時,會自動重新執行測試。有關更多 Jest CLI 選項,請參閱 Jest Docs

建立你的第一個測試

你的專案現在已準備好執行測試。根據 Jests 的慣例,將測試加入到專案根目錄中的 __tests__ 資料夾。

例如,我們可以加入一個測試來檢查 <Home /> 元件是否成功呈現標題:

// __tests__/index.test.jsx import { render, screen } from '@testing-library/react' import Home from '../pages/index' import '@testing-library/jest-dom' describe('Home', () => { it('renders a heading', () => { render(<Home />) const heading = screen.getByRole('heading', { name: /welcome to next\.js!/i, }) expect(heading).toBeInTheDocument() }) })

或者,加入 快照測試 以追蹤 <Home /> 元件有沒有任何的非預期變更:

// __tests__/snapshot.js import { render } from '@testing-library/react' import Home from '../pages/index' it('renders homepage unchanged', () => { const { container } = render(<Home />) expect(container).toMatchSnapshot() })

注意: 測試文件不應包含在 pages 資料夾中,因為 pages 資料夾中的任何文件都被視為路由。

執行你的測試

輸入 npm run test 來執行你的測試。在你的測試通過或失敗後,你會注意到一個互動式 Jest 指令列表,在你要加入更多測試時,這些指令會很有幫助。

延伸閱讀:

社群套件和範例

Next.js 社群建立了一些實用的套件和文章:

延伸閱讀:

本篇文章由warren30815

warren30815

貢獻翻譯。瞭解如何參與貢獻