跳转到内容

Next.js单元测试

来自代码酷
Admin留言 | 贡献2025年5月1日 (四) 23:18的版本 (Page creation by admin bot)

(差异) ←上一版本 | 已核准修订 (差异) | 最后版本 (差异) | 下一版本→ (差异)

Next.js单元测试[编辑 | 编辑源代码]

Next.js单元测试是指对Next.js应用程序中的独立模块(如页面组件、API路由、工具函数等)进行隔离验证的实践。通过模拟依赖项并断言预期行为,开发者能确保代码在迭代过程中保持正确性。本指南将涵盖基础概念、工具链配置、常见模式及实战案例。

核心概念[编辑 | 编辑源代码]

单元测试聚焦于最小可测试单元(通常是一个函数或组件),具有以下特点:

  • 隔离性:通过模拟(mock)外部依赖(如API调用、数据库)确保测试仅验证目标代码
  • 快速反馈:执行时间通常在毫秒级,适合开发中频繁运行
  • 确定性:相同输入始终产生相同输出,不受网络、环境等因素影响

在Next.js中常见测试对象包括:

  • 页面组件(pages/**/*.tsx
  • API路由(pages/api/**/*.ts
  • 工具函数(utils/*.ts
  • 自定义Hooks

工具链配置[编辑 | 编辑源代码]

推荐使用以下工具组合: ```bash

  1. 安装测试相关依赖

npm install --save-dev jest @testing-library/react @testing-library/jest-dom @testing-library/user-event ts-jest @types/jest ```

创建jest.config.js

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/$1', // 处理路径别名
  },
}

基础断言库配置(jest.setup.js):

import '@testing-library/jest-dom/extend-expect'

测试组件[编辑 | 编辑源代码]

基础组件测试[编辑 | 编辑源代码]

测试一个简单的Counter组件:

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

export function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <button onClick={() => setCount(c => c - 1)}>Decrement</button>
      <span data-testid="count-value">{count}</span>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  )
}

对应测试文件:

// components/Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { Counter } from './Counter'

describe('Counter', () => {
  it('should increment count', () => {
    render(<Counter />)
    const incrementButton = screen.getByText('Increment')
    fireEvent.click(incrementButton)
    expect(screen.getByTestId('count-value')).toHaveTextContent('1')
  })
})

页面组件测试[编辑 | 编辑源代码]

测试动态路由页面:

// pages/posts/[id].tsx
import { GetStaticProps } from 'next'

export default function Post({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

export const getStaticProps: GetStaticProps = async ({ params }) => {
  // 实际项目中这里会获取真实数据
  return { 
    props: { 
      post: { 
        id: params?.id, 
        title: `Post ${params?.id}`, 
        content: '...' 
      } 
    } 
  }
}

测试策略:

import { render, screen } from '@testing-library/react'
import Post from '../../pages/posts/[id]'

describe('Post page', () => {
  it('displays post content', () => {
    const mockPost = {
      id: '1',
      title: 'Test Post',
      content: 'This is a test'
    }
    
    render(<Post post={mockPost} />)
    expect(screen.getByRole('heading')).toHaveTextContent('Test Post')
    expect(screen.getByText('This is a test')).toBeInTheDocument()
  })
})

测试API路由[编辑 | 编辑源代码]

Next.js API路由本质是Node.js HTTP handlers,可使用node-mocks-http模拟请求:

// pages/api/hello.test.js
import { createMocks } from 'node-mocks-http'
import handler from './hello'

describe('/api/hello', () => {
  it('returns greeting message', async () => {
    const { req, res } = createMocks({
      method: 'GET'
    })

    await handler(req, res)
    expect(res._getStatusCode()).toBe(200)
    expect(JSON.parse(res._getData())).toEqual(
      expect.objectContaining({ message: 'Hello world' })
    )
  })
})

高级模式[编辑 | 编辑源代码]

快照测试[编辑 | 编辑源代码]

捕获组件渲染结果作为基准:

it('matches snapshot', () => {
  const { container } = render(<MyComponent />)
  expect(container.firstChild).toMatchSnapshot()
})

模拟Next.js路由[编辑 | 编辑源代码]

使用next-router-mock

jest.mock('next/router', () => require('next-router-mock'))

覆盖率报告[编辑 | 编辑源代码]

package.json中添加:

{
  "scripts": {
    "test:coverage": "jest --coverage"
  }
}

测试金字塔实践[编辑 | 编辑源代码]

pie title 测试类型分布 "单元测试" : 70 "集成测试" : 20 "E2E测试" : 10

常见问题解决[编辑 | 编辑源代码]

错误类型 解决方案
SyntaxError: Cannot use import statement outside a module 确保jest.config.js配置了transformpreset: 'ts-jest'
ReferenceError: document is not defined 设置testEnvironment: 'jsdom'
CSS模块导入失败 less)$': 'identity-obj-proxy' }

性能优化[编辑 | 编辑源代码]

  • 使用jest --watch仅运行修改文件的测试
  • 并行化测试:jest --maxWorkers=4
  • 虚拟DOM比较算法复杂度:O(n)(React的Reconciliation算法)

通过系统化的单元测试实践,Next.js应用可获得更高的代码健壮性和可维护性。建议将测试集成到CI/CD流程中,每次代码提交自动运行测试套件。