当前位置: 首页 > news >正文

测试你的 Next.-js 应用:Jest 和 React Testing Library

测试你的 Next.-js 应用:Jest 和 React Testing Library

作者:码力无边


到目前为止,我们已经构建了功能丰富的页面、API 和组件。我们的应用在本地开发环境中运行良好,但我们如何确保在未来的代码修改、重构或添加新功能时,不会无意中破坏现有的功能呢?答案就是——自动化测试

编写测试是专业软件开发的基石。它能:

  • 保证代码质量:在代码合并和部署前捕获 bug。
  • 提供安全网:让你能够自信地进行重构,因为测试会告诉你是否破坏了某些东西。
  • 充当文档:好的测试本身就是对组件行为的最佳描述。
  • 促进更好的设计:为了让代码可测试,你通常需要编写更模块化、更解耦的代码。

在 React 和 Next.js 的生态中,最流行和推荐的测试组合是 JestReact Testing Library (RTL)

  • Jest:一个功能全面的 JavaScript 测试运行器 (Test Runner)。它提供了测试结构 (describe, it, test)、断言库 (expect) 和 mock (模拟) 功能。
  • React Testing Library (RTL):一个专注于测试 React 组件的库。它的核心哲学是:“测试你的软件,就像用户使用它一样”。它鼓励你通过查询和交互的方式来测试组件,而不是深入其内部实现细节。

本文将指导你在 Next.js App Router 项目中配置 Jest 和 RTL,并编写几种常见的测试类型,包括对工具函数、React 组件和 API 路由的测试。

步骤一:环境配置

Next.js 官方提供了一个与 Jest 集成的示例,我们可以参考它来快速配置。

  1. 安装开发依赖

    npm install --save-dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom @types/jest
    
    • jest, @types/jest:Jest 核心。
    • jest-environment-jsdom:让 Jest 可以在一个模拟的浏览器环境 (JSDOM) 中运行,这对于测试 DOM 操作至关重要。
    • @testing-library/react:RTL 的核心,用于渲染组件和交互。
    • @testing-library/jest-dom:为 Jest 的 expect 提供了许多方便的、用于断言 DOM 状态的匹配器(如 toBeInTheDocument())。
  2. 配置 Jest (jest.config.js)

    在项目根目录创建 jest.config.js 文件。Next.js 官方提供了一个 next/jest 辅助函数,可以帮助我们处理 Babel、TypeScript 等复杂配置。

    // jest.config.js
    const nextJest = require('next/jest');const createJestConfig = nextJest({// 提供 next.config.js 和 .env 文件相对于此目录的路径dir: './',
    });// 添加任何你想自定义的 Jest 配置
    const customJestConfig = {setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],testEnvironment: 'jest-environment-jsdom',
    };// createJestConfig 被包裹以确保 next/jest 可以加载 Next.js 的配置
    module.exports = createJestConfig(customJestConfig);
    
  3. 创建 Jest 设置文件 (jest.setup.js)

    这个文件会在每个测试文件运行前执行,是放置全局设置的理想场所。我们在这里导入 @testing-library/jest-dom 来扩展 expect

    // jest.setup.js
    import '@testing-library/jest-dom';
    
  4. 配置 TypeScript (tsconfig.json)
    为了避免类型冲突,建议在 tsconfig.jsoninclude 数组中加入 Jest 配置文件。

    {"compilerOptions": { ... },"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "jest.config.js", "jest.setup.js"],"exclude": ["node_modules"]
    }
    
  5. package.json 中添加测试脚本

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

配置完成!现在我们可以开始编写测试了。

实战一:测试简单的工具函数 (单元测试)

这是最简单的测试类型。假设我们有一个计算总价的工具函数。

lib/utils.ts

export function calculateTotalPrice(price: number, quantity: number): number {if (price < 0 || quantity < 0) {throw new Error('价格和数量不能为负数');}return price * quantity;
}

__tests__/lib/utils.test.ts (通常将测试文件放在 __tests__ 目录下,或与源文件同级的 .test.ts 文件)

import { calculateTotalPrice } from '../../lib/utils';// `describe` 用于将相关的测试分组
describe('calculateTotalPrice', () => {// `it` 或 `test` 用于定义一个测试用例it('应该正确计算总价', () => {// 断言:期望 10 * 2 的结果是 20expect(calculateTotalPrice(10, 2)).toBe(20);});it('应该能处理数量为0的情况', () => {expect(calculateTotalPrice(10, 0)).toBe(0);});it('当输入为负数时应该抛出错误', () => {// 测试函数是否按预期抛出错误expect(() => calculateTotalPrice(-10, 2)).toThrow('价格和数量不能为负数');});
});

运行 npm test,你应该能看到测试通过的绿色提示。

实战二:测试 React 组件 (组件测试)

这是 RTL 的核心舞台。我们将测试一个简单的计数器组件。

components/Counter.tsx

"use client";
import { useState } from 'react';export default function Counter() {const [count, setCount] = useState(0);return (<div><p>当前计数: {count}</p><button onClick={() => setCount(count + 1)}>增加</button></div>);
}

__tests__/components/Counter.test.tsx

import { render, screen, fireEvent } from '@testing-library/react';
import Counter from '../../components/Counter';describe('Counter component', () => {it('应该正确渲染初始状态', () => {// 1. 渲染组件render(<Counter />);// 2. 查询元素// screen 对象提供了多种查询方法,getByText 是其中之一const countElement = screen.getByText(/当前计数: 0/i);const buttonElement = screen.getByRole('button', { name: /增加/i });// 3. 断言expect(countElement).toBeInTheDocument();expect(buttonElement).toBeInTheDocument();});it('点击按钮后计数应该增加', () => {render(<Counter />);const buttonElement = screen.getByRole('button', { name: /增加/i });// 模拟用户行为:点击按钮fireEvent.click(buttonElement);// 查询更新后的元素const countElement = screen.getByText(/当前计数: 1/i);// 断言状态已更新expect(countElement).toBeInTheDocument();});
});

RTL 核心思想

  • render: 将你的组件渲染到 JSDOM 中。
  • screen: 提供查询 DOM 的方法,鼓励使用用户可见的方式查询(如 getByRole, getByText, getByLabelText),而不是 getByTestId 或 class/id 选择器。
  • fireEvent / userEvent: 模拟用户交互,如点击、输入等 (user-event 是一个更接近真实用户行为的库,推荐在复杂交互中使用)。

实战三:测试 Server Components (及数据获取)

测试 Server Components 稍微复杂一些,因为它们是 async 的,并且可能依赖外部数据。我们需要使用 mock 来模拟 fetch 或其他数据源。

app/posts/[id]/page.tsx (一个简化的文章详情页)

async function getPost(id: string) {const res = await fetch(`https://api.example.com/posts/${id}`);if (!res.ok) throw new Error('Failed to fetch');return res.json();
}export default async function PostPage({ params }: { params: { id: string } }) {const post = await getPost(params.id);return (<article><h1>{post.title}</h1><p>{post.body}</p></article>);
}

__tests__/app/posts/[id]/page.test.tsx

import { render, screen } from '@testing-library/react';
import PostPage from '../../../../app/posts/[id]/page';// Mock 全局的 fetch 函数
global.fetch = jest.fn();describe('PostPage', () => {it('应该能正确获取并渲染文章数据', async () => {const mockPost = {id: 1,title: '我的第一篇测试文章',body: '这是文章的内容。',};// 配置 fetch mock 的返回值(global.fetch as jest.Mock).mockResolvedValueOnce({ok: true,json: async () => mockPost,});// Server Components 返回 Promise,所以我们需要 await render 的结果const PageComponent = await PostPage({ params: { id: '1' } });render(PageComponent);// 使用 findBy* 方法,它会等待异步操作完成const titleElement = await screen.findByRole('heading', { name: mockPost.title });const bodyElement = screen.getByText(mockPost.body);expect(titleElement).toBeInTheDocument();expect(bodyElement).toBeInTheDocument();});
});

总结

测试是构建高质量、可维护的 Next.js 应用不可或缺的一环。虽然初期的配置和学习需要投入一些时间,但它带来的长期收益是巨大的。

核心测试策略回顾:

  1. 单元测试:使用 Jest 测试独立的、纯粹的逻辑(如工具函数),确保其输入和输出符合预期。
  2. 组件测试:使用 React Testing Library 测试组件。遵循“像用户一样测试”的原则,关注组件的渲染输出和交互行为,而非其内部实现。
  3. Mocking:当测试依赖于外部系统(如 API, 数据库)时,使用 Jest 的 mock 功能来模拟这些依赖,使测试变得确定和快速。

将自动化测试集成到你的开发流程中,你将能够更自信地发布新版本,更快地迭代产品,并构建出更健壮、更可靠的应用程序。

在下一篇文章中,我们将探讨国际化 (i18n),学习如何构建一个支持多种语言的 Next.js 网站,以触达更广泛的全球用户。敬请期待!


文章转载自:

http://ivBdMLwd.Lzsxp.cn
http://iVtJTeL4.Lzsxp.cn
http://ZvEFcM26.Lzsxp.cn
http://Mu9rsGdD.Lzsxp.cn
http://oYWCgOnV.Lzsxp.cn
http://yKM3A9DS.Lzsxp.cn
http://zi75stO7.Lzsxp.cn
http://fmwFK6Fo.Lzsxp.cn
http://FKzRlpJF.Lzsxp.cn
http://xe0FemI2.Lzsxp.cn
http://9Bzc95Zz.Lzsxp.cn
http://YQsOaisd.Lzsxp.cn
http://A7XuSMe1.Lzsxp.cn
http://bWPs99Yb.Lzsxp.cn
http://l64MVH5M.Lzsxp.cn
http://OG0tPXNB.Lzsxp.cn
http://RqLgSfXT.Lzsxp.cn
http://Vjy8RArO.Lzsxp.cn
http://GNIOtdhs.Lzsxp.cn
http://YDkNnwoQ.Lzsxp.cn
http://olBl0So2.Lzsxp.cn
http://rDBlZLrT.Lzsxp.cn
http://JMry5CCc.Lzsxp.cn
http://Oc2378hG.Lzsxp.cn
http://TwncbC1z.Lzsxp.cn
http://fRSFAofK.Lzsxp.cn
http://4WoRNucL.Lzsxp.cn
http://3mMt1rop.Lzsxp.cn
http://nMcLGvVu.Lzsxp.cn
http://EqO0sXaI.Lzsxp.cn
http://www.dtcms.com/a/387707.html

相关文章:

  • 第二十二篇|新世界语学院教育数据深度解析:学制函数、能力矩阵与升学图谱
  • n8n自动化工作流学习笔记-生成动物跳水视频
  • 如何用快慢指针优雅地找到链表的中间结点?——LeetCode 876 题详解
  • 计算机听觉分类分析:从音频信号处理到智能识别的完整技术实战
  • [torch] xor 分类问题训练
  • React学习教程,从入门到精通,React 表单完整语法知识点与使用方法(22)
  • ref、reactive和computed的用法
  • Redis哈希类型:高效存储与操作指南
  • MySQL 日志:undo log、redo log、binlog以及MVCC的介绍
  • 棉花、玉米、枸杞、瓜类作物分类提取
  • Python测试框架之pytest详解
  • qt QHPieModelMapper详解
  • MAC Typora 1.8.10无法打开多个md档
  • 零碳园区的 “追光者”:三轴光伏太阳花的技术创新与应用逻辑
  • MAC-Java枚举工具类实现
  • 「数据获取」全国村级点状矢量数据
  • Chromium 138 编译指南 macOS 篇:源代码获取(四)
  • 人工智能概念:NLP任务的评估指标(BLEU、ROUGE、PPL、BERTScore、RAGAS)
  • 机器学习基础:从线性回归到多分类实战
  • 深度学习基础:线性回归与 Softmax 回归全解析,从回归到分类的桥梁
  • Scikit-learn Python机器学习 - 分类算法 - 决策树
  • 【人工智能agent】--dify实现文找图、图找文、图找图
  • 基于 Landsat-8 数据的甘肃省金塔县主要农作物分类
  • 社区补丁的回复及常用链接
  • Pyside6 + QML - 信号与槽01 - Button 触发 Python 类方法
  • 视频理解学习笔记
  • Android Studio 将SVG资源转换成生成xml图
  • 后台管理系统详解:通用的系统架构介绍与说明
  • r-DMT市场报告:深度解析全球研究现状与未来发展趋势
  • 企业网络里的API安全防护指南