测试
示例项目
学习如何配置 Next.js 与常用测试工具: Cypress, Playwright 和 Jest 与 React Testing Library。
Cypress
Cypress 是一个用于 端到端测试 (E2E) 和 组件测试 的测试运行器。
快速开始
您可以使用 create-next-app
配合 with-cypress 示例项目 快速上手。
npx create-next-app@latest --example with-cypress with-cypress-app
手动配置
要开始使用 Cypress,请先安装 cypress
包:
npm install --save-dev cypress
在 package.json
的 scripts 字段中添加 Cypress:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"cypress:open": "cypress open"
}
}
首次运行 Cypress 以生成使用推荐目录结构的示例文件:
npm run cypress:open
您可以浏览生成的示例文件,并参考 Cypress 文档中的 编写第一个测试 部分来熟悉 Cypress。
应该使用 E2E 测试还是组件测试?
Cypress 文档提供了指南 说明这两种测试类型的区别及各自的适用场景。
创建第一个 Cypress E2E 测试
假设有以下两个 Next.js 页面:
import Link from 'next/link'
export default function Home() {
return (
<nav>
<h1>首页</h1>
<Link href="/about">关于</Link>
</nav>
)
}
export default function About() {
return (
<div>
<h1>关于页面</h1>
<Link href="/">首页</Link>
</div>
)
}
添加测试来验证导航功能是否正常:
describe('导航测试', () => {
it('应能跳转到关于页面', () => {
// 从首页开始
cy.visit('http://localhost:3000/')
// 找到包含 "about" 的链接并点击
cy.get('a[href*="about"]').click()
// 新 URL 应包含 "/about"
cy.url().should('include', '/about')
// 新页面应包含 "About Page" 的 h1 标签
cy.get('h1').contains('About Page')
})
})
如果在 cypress.config.js
配置文件中添加 baseUrl: 'http://localhost:3000'
,就可以使用 cy.visit("/")
替代 cy.visit("http://localhost:3000/")
。
创建第一个 Cypress 组件测试
组件测试会构建并挂载特定组件,而无需打包整个应用或启动服务器。这使得测试更高效,同时仍能提供可视化反馈和与 E2E 测试相同的 API。
须知:由于组件测试不启动 Next.js 服务器,依赖服务器的功能如
<Image />
和getServerSideProps
将无法直接使用。参考 Cypress Next.js 文档 了解如何在组件测试中启用这些功能。
假设使用与上一节相同的组件,添加测试来验证组件是否渲染了预期内容:
import AboutPage from './about.js'
describe('<AboutPage />', () => {
it('应渲染并显示预期内容', () => {
// 挂载 About 页面的 React 组件
cy.mount(<AboutPage />)
// 页面应包含 "About Page" 的 h1 标签
cy.get('h1').contains('About Page')
// 验证包含预期 URL 的链接是否存在
// *跟随*链接更适合在 E2E 测试中进行
cy.get('a[href="/"]').should('be.visible')
})
})
运行 Cypress 测试
E2E 测试
由于 Cypress E2E 测试需要测试真实的 Next.js 应用,因此需要先启动 Next.js 服务器。建议针对生产代码运行测试,以更接近实际应用行为。
先运行 npm run build
和 npm run start
,然后在另一个终端窗口中运行 npm run cypress -- --e2e
来启动 Cypress 并运行 E2E 测试套件。
须知:也可以安装
start-server-and-test
包并添加到package.json
的 scripts 字段:"test": "start-server-and-test start http://localhost:3000 cypress"
,以便在启动 Next.js 生产服务器的同时运行 Cypress。记得在代码变更后重新构建应用。
组件测试
运行 npm run cypress -- --component
来启动 Cypress 并执行组件测试套件。
为持续集成 (CI) 做准备
您可能注意到目前运行 Cypress 会打开交互式浏览器,这不适合 CI 环境。您也可以使用 cypress run
命令以无头模式运行 Cypress:
{
"scripts": {
//...
"e2e": "start-server-and-test dev http://localhost:3000 \"cypress open --e2e\"",
"e2e:headless": "start-server-and-test dev http://localhost:3000 \"cypress run --e2e\"",
"component": "cypress open --component",
"component:headless": "cypress run --component"
}
}
您可以从以下资源了解更多关于 Cypress 和持续集成的信息:
Playwright
Playwright 是一个测试框架,允许您通过单一 API 自动化 Chromium、Firefox 和 WebKit。您可以用它来编写跨平台的 端到端测试 (E2E) 和 集成测试。
快速开始
最快的方式是使用 create-next-app
配合 with-playwright 示例项目,这将创建一个已配置好 Playwright 的 Next.js 项目。
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
在 package.json
的 scripts 字段中添加 Playwright:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"test:e2e": "playwright test"
}
}
创建第一个 Playwright 端到端测试
假设有以下两个 Next.js 页面:
import Link from 'next/link'
export default function Home() {
return (
<nav>
<Link href="/about">关于</Link>
</nav>
)
}
export default function About() {
return (
<div>
<h1>关于页面</h1>
</div>
)
}
添加测试来验证导航功能是否正常:
import { test, expect } from '@playwright/test'
test('应能跳转到关于页面', async ({ page }) => {
// 从首页开始(baseURL 通过 playwright.config.ts 中的 webServer 设置)
await page.goto('http://localhost:3000/')
// 找到包含 'About' 文本的元素并点击
await page.click('text=About')
// 新 URL 应为 "/about"(使用了 baseURL)
await expect(page).toHaveURL('http://localhost:3000/about')
// 新页面应包含 "About Page" 的 h1 标签
await expect(page.locator('h1')).toContainText('About Page')
})
import { test, expect } from '@playwright/test'
test('应能跳转到关于页面', async ({ page }) => {
// 从首页开始(baseURL 通过 playwright.config.ts 中的 webServer 设置)
await page.goto('http://localhost:3000/')
// 找到包含 'About' 文本的元素并点击
await page.click('text=About')
// 新 URL 应为 "/about"(使用了 baseURL)
await expect(page).toHaveURL('http://localhost:3000/about')
// 新页面应包含 "About Page" 的 h1 标签
await expect(page.locator('h1')).toContainText('About Page')
})
如果在 playwright.config.ts
配置文件中添加 "baseURL": "http://localhost:3000"
,就可以使用 page.goto("/")
替代 page.goto("http://localhost:3000/")
。
运行 Playwright 测试
由于 Playwright 测试的是真实的 Next.js 应用,需要先启动 Next.js 服务器。建议针对生产代码运行测试,以更接近实际应用行为。
先运行 npm run build
和 npm run start
,然后在另一个终端窗口中运行 npm run test:e2e
来执行 Playwright 测试。
须知:您也可以使用
webServer
功能让 Playwright 启动开发服务器并等待其完全可用。
在持续集成 (CI) 上运行 Playwright
Playwright 默认会在 无头模式 下运行测试。要安装所有 Playwright 依赖,请运行 npx playwright install-deps
。
您可以从以下资源了解更多关于 Playwright 和持续集成的信息:
Jest 和 React Testing Library
Jest 和 React Testing Library 通常一起用于 单元测试。有三种方式可以在 Next.js 应用中使用 Jest:
- 使用我们的 快速开始示例
- 通过 Next.js Rust 编译器
- 通过 Babel
以下部分将介绍如何使用这些选项配置 Jest:
快速开始
您可以使用 create-next-app
配合 with-jest 示例项目 快速上手 Jest 和 React Testing Library:
npx create-next-app@latest --example with-jest with-jest-app
配置 Jest (使用 Rust 编译器)
自 Next.js 12 发布以来,Next.js 已内置了对 Jest 的配置支持。
要配置 Jest,请安装 jest
, jest-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.mjs
文件并添加以下内容:
import nextJest from 'next/jest.js'
const createJestConfig = nextJest({
// 提供 Next.js 应用的路径以在测试环境中加载 next.config.js 和 .env 文件
dir: './',
})
// 添加要传递给 Jest 的自定义配置
/** @type {import('jest').Config} */
const config = {
// 在每个测试运行前添加更多设置选项
// setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
}
// 这样导出 createJestConfig 以确保 next/jest 能加载异步的 Next.js 配置
export default createJestConfig(config)
在底层,next/jest
会自动为您配置 Jest,包括:
- 使用 SWC 设置
transform
- 自动模拟样式表 (
.css
,.module.css
及其 scss 变体)、图片导入和next/font
- 将
.env
(及其所有变体) 加载到process.env
- 从测试解析和转换中忽略
node_modules
- 从测试解析中忽略
.next
- 加载
next.config.js
以启用 SWC 转换的标志
须知:要直接测试环境变量,请在单独的设置脚本或
jest.config.js
文件中手动加载它们。更多信息请参考 测试环境变量。
配置 Jest (搭配 Babel)
如果选择不使用 Rust 编译器,除了上述包外,您需要手动配置 Jest 并安装 babel-jest
和 identity-obj-proxy
。
以下是针对 Next.js 推荐的 Jest 配置选项:
module.exports = {
collectCoverage: true,
// 在 node 14.x 上,coverageProvider 使用 v8 能提供良好的速度和基本准确的报告
coverageProvider: 'v8',
collectCoverageFrom: [
'**/*.{js,jsx,ts,tsx}',
'!**/*.d.ts',
'!**/node_modules/**',
'!<rootDir>/out/**',
'!<rootDir>/.next/**',
'!<rootDir>/*.config.js',
'!<rootDir>/coverage/**',
],
moduleNameMapper: {
// 处理 CSS 导入 (使用 CSS 模块)
// https://jestjs.io/docs/webpack#mocking-css-modules
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
// 处理 CSS 导入 (不使用 CSS 模块)
'^.+\\.(css|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',
// 处理图片导入
// https://jestjs.io/docs/webpack#handling-static-assets
'^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)$/i': `<rootDir>/__mocks__/fileMock.js`,
// 处理模块别名
'^@/components/(.*)$': '<rootDir>/components/$1',
},
// 在每个测试运行前添加更多设置选项
// setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
testEnvironment: 'jsdom',
transform: {
// 使用 babel-jest 通过 next/babel 预设转译测试文件
// 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.js
和 styleMock.js
:
module.exports = {
src: '/img.jpg',
height: 24,
width: 24,
blurDataURL: 'data:image/png;base64,imagedata',
}
module.exports = {}
有关处理静态资源的更多信息,请参阅 Jest 文档。
可选:使用自定义匹配器扩展 Jest
@testing-library/jest-dom
包含一组方便的 自定义匹配器,例如 .toBeInTheDocument()
,使编写测试更加容易。您可以通过在 Jest 配置文件中添加以下选项,为每个测试导入自定义匹配器:
setupFilesAfterEnv: ['<rootDir>/jest.setup.js']
然后,在 jest.setup.js
中添加以下导入:
import '@testing-library/jest-dom'
extend-expect
在v6.0
中被移除,因此如果您使用的是@testing-library/jest-dom
6.0 之前的版本,则需要导入@testing-library/jest-dom/extend-expect
。
如果您需要在每个测试前添加更多设置选项,通常可以将它们添加到上述的 jest.setup.js
文件中。
可选:绝对导入和模块路径别名
如果您的项目使用了 模块路径别名,则需要配置 Jest 以解析这些导入,方法是将 jsconfig.json
文件中的 paths
选项与 jest.config.js
文件中的 moduleNameMapper
选项匹配。例如:
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "node",
"baseUrl": "./",
"paths": {
"@/components/*": ["components/*"]
}
}
}
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 文档。
创建您的第一个测试
您的项目现在已准备好运行测试。按照 Jest 的约定,在项目的根目录下的 __tests__
文件夹中添加测试。
例如,我们可以添加一个测试来检查 <Home />
组件是否成功渲染了一个标题:
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 />
组件的任何意外更改:
import { render } from '@testing-library/react'
import Home from '../pages/index'
it('renders homepage unchanged', () => {
const { container } = render(<Home />)
expect(container).toMatchSnapshot()
})
须知:测试文件不应包含在 Pages Router 中,因为 Pages Router 中的任何文件都被视为路由。
运行测试套件
运行 npm run test
来执行测试套件。在测试通过或失败后,您会看到一系列交互式 Jest 命令,这些命令在您添加更多测试时会很有帮助。
如需进一步阅读,以下资源可能会对您有所帮助:
- Jest 文档
- React Testing Library 文档
- Testing Playground - 使用良好的测试实践来匹配元素。
社区包和示例
Next.js 社区创建了一些您可能会觉得有用的包和文章:
- next-router-mock 用于 Storybook。
- 使用 Cypress 测试 Vercel 部署预览 作者 Gleb Bahmutov。
有关下一步阅读的内容,我们推荐:
- pages/basic-features/environment-variables#test-environment-variables