测试

示例项目

学习如何配置 Next.js 与常用测试工具: Cypress, PlaywrightJest 与 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:

package.json
{
  "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 页面:

pages/index.js
import Link from 'next/link'

export default function Home() {
  return (
    <nav>
      <h1>首页</h1>
      <Link href="/about">关于</Link>
    </nav>
  )
}
pages/about.js
export default function About() {
  return (
    <div>
      <h1>关于页面</h1>
      <Link href="/">首页</Link>
    </div>
  )
}

添加测试来验证导航功能是否正常:

cypress/e2e/app.cy.js
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 文档 了解如何在组件测试中启用这些功能。

假设使用与上一节相同的组件,添加测试来验证组件是否渲染了预期内容:

pages/about.cy.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 buildnpm 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:

package.json
{
  "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:

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">关于</Link>
    </nav>
  )
}
pages/about.js
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')
})

如果在 playwright.config.ts 配置文件中添加 "baseURL": "http://localhost:3000",就可以使用 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 Testing Library

Jest 和 React Testing Library 通常一起用于 单元测试。有三种方式可以在 Next.js 应用中使用 Jest:

  1. 使用我们的 快速开始示例
  2. 通过 Next.js Rust 编译器
  3. 通过 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 文件并添加以下内容:

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-jestidentity-obj-proxy

以下是针对 Next.js 推荐的 Jest 配置选项:

jest.config.js
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.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 文档

可选:使用自定义匹配器扩展 Jest

@testing-library/jest-dom 包含一组方便的 自定义匹配器,例如 .toBeInTheDocument(),使编写测试更加容易。您可以通过在 Jest 配置文件中添加以下选项,为每个测试导入自定义匹配器:

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

然后,在 jest.setup.js 中添加以下导入:

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

extend-expectv6.0 中被移除,因此如果您使用的是 @testing-library/jest-dom 6.0 之前的版本,则需要导入 @testing-library/jest-dom/extend-expect

如果您需要在每个测试前添加更多设置选项,通常可以将它们添加到上述的 jest.setup.js 文件中。

可选:绝对导入和模块路径别名

如果您的项目使用了 模块路径别名,则需要配置 Jest 以解析这些导入,方法是将 jsconfig.json 文件中的 paths 选项与 jest.config.js 文件中的 moduleNameMapper 选项匹配。例如:

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

创建测试:

在 package.json 中添加测试脚本

将 Jest 可执行文件以监视模式添加到 package.json 的脚本中:

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

jest --watch 会在文件更改时重新运行测试。有关更多 Jest CLI 选项,请参阅 Jest 文档

创建您的第一个测试

您的项目现在已准备好运行测试。按照 Jest 的约定,在项目的根目录下的 __tests__ 文件夹中添加测试。

例如,我们可以添加一个测试来检查 <Home /> 组件是否成功渲染了一个标题:

__tests__/index.test.js
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 Router 中,因为 Pages Router 中的任何文件都被视为路由。

运行测试套件

运行 npm run test 来执行测试套件。在测试通过或失败后,您会看到一系列交互式 Jest 命令,这些命令在您添加更多测试时会很有帮助。

如需进一步阅读,以下资源可能会对您有所帮助:

社区包和示例

Next.js 社区创建了一些您可能会觉得有用的包和文章:

有关下一步阅读的内容,我们推荐:

  • pages/basic-features/environment-variables#test-environment-variables