06-高级模式与实战项目——09. 单元测试 - Jest

发布时间:2026/7/18 17:11:16

06-高级模式与实战项目——09. 单元测试 - Jest
09. 单元测试 - Jest概述Jest 是 Facebook 开源的 JavaScript 测试框架广泛用于 React 应用的单元测试。它提供测试运行器、断言库、Mock 功能、代码覆盖率等一站式解决方案。维度内容What使用 Jest 测试 JavaScript/React 代码的单元Why确保代码正确性提高代码质量When测试函数、工具类、自定义 HooksWhere__tests__目录或.test.js文件Who需要保证代码质量的开发者Howtest(adds 1 2 to equal 3, () { expect(add(1, 2)).toBe(3) })1. Jest 基础1.1 安装配置npminstall--save-dev jest types/jest// package.json{scripts:{test:jest,test:watch:jest --watch,test:coverage:jest --coverage}}1.2 基本测试结构// math.js export function add(a, b) { return a b; } export function subtract(a, b) { return a - b; } export function multiply(a, b) { return a * b; } export function divide(a, b) { if (b 0) throw new Error(除数不能为0); return a / b; } // math.test.js import { add, subtract, multiply, divide } from ./math; describe(数学函数, () { test(add 函数: 1 2 3, () { expect(add(1, 2)).toBe(3); }); test(subtract 函数: 5 - 3 2, () { expect(subtract(5, 3)).toBe(2); }); test(multiply 函数: 2 * 3 6, () { expect(multiply(2, 3)).toBe(6); }); test(divide 函数: 6 / 2 3, () { expect(divide(6, 2)).toBe(3); }); test(divide 函数: 除以0抛出错误, () { expect(() divide(6, 0)).toThrow(除数不能为0); }); });2. 常用断言2.1 相等性断言test(相等性断言, () { // 基本相等 expect(1 1).toBe(2); // 对象相等深度比较 expect({ name: 张三 }).toEqual({ name: 张三 }); // 不相等 expect(1 1).not.toBe(3); // 严格相等SameValue expect(NaN).toBeNaN(); expect(0).not.toBe(-0); });2.2 真值断言test(真值断言, () { // 真值 expect(true).toBeTruthy(); expect(1).toBeTruthy(); expect(hello).toBeTruthy(); // 假值 expect(false).toBeFalsy(); expect(0).toBeFalsy(); expect().toBeFalsy(); expect(null).toBeFalsy(); expect(undefined).toBeFalsy(); // 特定值 expect(null).toBeNull(); expect(undefined).toBeUndefined(); expect(hello).toBeDefined(); });2.3 数值断言test(数值断言, () { expect(5).toBeGreaterThan(3); // expect(5).toBeGreaterThanOrEqual(5); // expect(3).toBeLessThan(5); // expect(3).toBeLessThanOrEqual(3); // expect(0.1 0.2).toBeCloseTo(0.3); // 浮点数 });2.4 数组和对象断言test(数组和对象断言, () { // 数组 expect([1, 2, 3]).toContain(2); expect([1, 2, 3]).toHaveLength(3); // 对象 expect({ name: 张三, age: 18 }).toHaveProperty(name); expect({ name: 张三, age: 18 }).toHaveProperty(age, 18); // 字符串 expect(hello world).toContain(world); expect(hello world).toMatch(/world/); });3. 异步测试3.1 Promise 测试// api.js export function fetchUser(id) { return fetch(/api/users/${id}).then(res res.json()); } // api.test.js import { fetchUser } from ./api; // 使用 async/await test(fetchUser 返回用户数据, async () { const user await fetchUser(1); expect(user).toHaveProperty(id, 1); }); // 使用 resolves test(fetchUser 返回用户数据 (resolves), () { return expect(fetchUser(1)).resolves.toHaveProperty(id, 1); }); // 测试错误 test(fetchUser 失败时抛出错误, async () { await expect(fetchUser(999)).rejects.toThrow(); });3.2 回调测试// callback.js export function fetchData(callback) { setTimeout(() { callback({ data: success }); }, 100); } // callback.test.js import { fetchData } from ./callback; test(fetchData 调用回调, (done) { fetchData((data) { expect(data).toEqual({ data: success }); done(); // 等待回调完成 }); });4. Mock 功能4.1 函数 Mock// 创建 Mock 函数 test(Mock 函数, () { const mockFn jest.fn(); mockFn(hello, world); mockFn(again); // 调用断言 expect(mockFn).toHaveBeenCalled(); expect(mockFn).toHaveBeenCalledTimes(2); expect(mockFn).toHaveBeenCalledWith(hello, world); expect(mockFn).toHaveBeenLastCalledWith(again); }); // Mock 返回值 const mockFn jest.fn() .mockReturnValueOnce(first) .mockReturnValueOnce(second) .mockReturnValue(default); console.log(mockFn()); // first console.log(mockFn()); // second console.log(mockFn()); // default // Mock 实现 const mockFn jest.fn((x) x * 2); console.log(mockFn(5)); // 104.2 模块 Mock// __mocks__/axios.js export default { get: jest.fn(() Promise.resolve({ data: { users: [] } })), post: jest.fn(() Promise.resolve({ data: { success: true } })), }; // userService.test.js import axios from axios; import { getUsers, createUser } from ./userService; jest.mock(axios); test(getUsers 返回用户列表, async () { const mockUsers [{ id: 1, name: 张三 }]; axios.get.mockResolvedValue({ data: mockUsers }); const users await getUsers(); expect(users).toEqual(mockUsers); expect(axios.get).toHaveBeenCalledWith(/api/users); }); test(createUser 创建用户, async () { const newUser { name: 李四 }; axios.post.mockResolvedValue({ data: { id: 2, ...newUser } }); const user await createUser(newUser); expect(user).toHaveProperty(id, 2); });4.3 时间 Mock// 使用假计时器 jest.useFakeTimers(); test(延迟执行, () { const callback jest.fn(); setTimeout(callback, 1000); jest.advanceTimersByTime(500); expect(callback).not.toHaveBeenCalled(); jest.advanceTimersByTime(500); expect(callback).toHaveBeenCalled(); });5. 测试 React 组件5.1 工具函数测试// utils/format.js export function formatDate(date) { return new Date(date).toLocaleDateString(zh-CN); } export function formatPrice(price) { return ¥${price.toFixed(2)}; } export function validateEmail(email) { return /\S\S\.\S/.test(email); } // utils/format.test.js import { formatDate, formatPrice, validateEmail } from ./format; describe(格式化函数, () { test(formatDate 格式化日期, () { expect(formatDate(2024-01-15)).toBe(2024/1/15); }); test(formatPrice 格式化价格, () { expect(formatPrice(19.9)).toBe(¥19.90); expect(formatPrice(100)).toBe(¥100.00); }); test(validateEmail 验证邮箱, () { expect(validateEmail(testexample.com)).toBe(true); expect(validateEmail(invalid)).toBe(false); }); });5.2 自定义 Hook 测试// hooks/useCounter.js import { useState, useCallback } from react; export function useCounter(initialValue 0) { const [count, setCount] useState(initialValue); const increment useCallback(() setCount(c c 1), []); const decrement useCallback(() setCount(c c - 1), []); const reset useCallback(() setCount(initialValue), [initialValue]); return { count, increment, decrement, reset }; } // hooks/useCounter.test.js import { renderHook, act } from testing-library/react; import { useCounter } from ./useCounter; describe(useCounter, () { test(初始值为 0, () { const { result } renderHook(() useCounter()); expect(result.current.count).toBe(0); }); test(可以设置初始值, () { const { result } renderHook(() useCounter(10)); expect(result.current.count).toBe(10); }); test(increment 增加计数, () { const { result } renderHook(() useCounter()); act(() { result.current.increment(); }); expect(result.current.count).toBe(1); }); test(decrement 减少计数, () { const { result } renderHook(() useCounter(5)); act(() { result.current.decrement(); }); expect(result.current.count).toBe(4); }); test(reset 重置计数, () { const { result } renderHook(() useCounter(10)); act(() { result.current.increment(); result.current.increment(); result.current.reset(); }); expect(result.current.count).toBe(10); }); });6. 完整示例// services/todoService.js const API_URL https://api.example.com/todos; export async function fetchTodos() { const response await fetch(API_URL); if (!response.ok) throw new Error(获取失败); return response.json(); } export async function createTodo(todo) { const response await fetch(API_URL, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(todo), }); if (!response.ok) throw new Error(创建失败); return response.json(); } export async function updateTodo(id, updates) { const response await fetch(${API_URL}/${id}, { method: PATCH, headers: { Content-Type: application/json }, body: JSON.stringify(updates), }); if (!response.ok) throw new Error(更新失败); return response.json(); } export async function deleteTodo(id) { const response await fetch(${API_URL}/${id}, { method: DELETE }); if (!response.ok) throw new Error(删除失败); return true; } // services/todoService.test.js import { fetchTodos, createTodo, updateTodo, deleteTodo } from ./todoService; global.fetch jest.fn(); describe(Todo Service, () { beforeEach(() { fetch.mockClear(); }); describe(fetchTodos, () { test(成功获取 todos, async () { const mockTodos [{ id: 1, title: 测试, completed: false }]; fetch.mockResolvedValue({ ok: true, json: async () mockTodos, }); const todos await fetchTodos(); expect(todos).toEqual(mockTodos); expect(fetch).toHaveBeenCalledWith(https://api.example.com/todos); }); test(获取失败时抛出错误, async () { fetch.mockResolvedValue({ ok: false }); await expect(fetchTodos()).rejects.toThrow(获取失败); }); }); describe(createTodo, () { test(成功创建 todo, async () { const newTodo { title: 新任务, completed: false }; const createdTodo { id: 1, ...newTodo }; fetch.mockResolvedValue({ ok: true, json: async () createdTodo, }); const result await createTodo(newTodo); expect(result).toEqual(createdTodo); expect(fetch).toHaveBeenCalledWith( https://api.example.com/todos, expect.objectContaining({ method: POST }) ); }); }); describe(updateTodo, () { test(成功更新 todo, async () { const updates { completed: true }; const updatedTodo { id: 1, title: 测试, completed: true }; fetch.mockResolvedValue({ ok: true, json: async () updatedTodo, }); const result await updateTodo(1, updates); expect(result).toEqual(updatedTodo); expect(fetch).toHaveBeenCalledWith( https://api.example.com/todos/1, expect.objectContaining({ method: PATCH }) ); }); }); describe(deleteTodo, () { test(成功删除 todo, async () { fetch.mockResolvedValue({ ok: true }); const result await deleteTodo(1); expect(result).toBe(true); expect(fetch).toHaveBeenCalledWith( https://api.example.com/todos/1, expect.objectContaining({ method: DELETE }) ); }); test(删除失败时抛出错误, async () { fetch.mockResolvedValue({ ok: false }); await expect(deleteTodo(1)).rejects.toThrow(删除失败); }); }); });7. 总结核心要点要点说明测试结构describe test/ it断言expect matcher异步测试async/await、resolves、rejectsMockjest.fn()、jest.mock()记忆口诀Jest 测试框架强describe 分组 test 写expect 断言各种值异步测试要等待Mock 模拟外部依赖代码覆盖率不用愁8. 相关资源Jest 官方文档React Testing Library

相关新闻

储能沙盘模型BMS功能模拟控制系统设计与实现:电压监测、均衡管理与故障保护的STM32方案

储能沙盘模型BMS功能模拟控制系统设计与实现:电压监测、均衡管理与故障保护的STM32方案

2026/7/10 18:35:32

摘要 电池管理系统(BMS)是储能系统的核心控制单元,负责电芯电压监测、温度监测、均衡管理及故障保护等关键功能。在储能沙盘模型中,BMS功能的可视化模拟是区别“外观模型”与“教学演示模型”的分水岭。 本文结合筑城世纪模型在储…

无监督聚类评估:3大内部指标(轮廓系数/CH指数/DB指数)的数学直觉与选型指南

无监督聚类评估:3大内部指标(轮廓系数/CH指数/DB指数)的数学直觉与选型指南

2026/7/10 18:35:36

无监督聚类评估:三大内部指标的数学直觉与实战选型当数据科学家面对没有标签的数据集时,如何判断聚类结果的质量?本文将深入解析轮廓系数(SC)、Calinski-Harabaz指数(CH)和Davies-Bouldin指数&a…

exe 文件探测开发语言检测-软件分析—东方仙盟

exe 文件探测开发语言检测-软件分析—东方仙盟

2026/7/10 21:30:31

ExeinfoPe 完整工具介绍正文1. 基础定义ExeinfoPe 是一款免费、绿色免安装的 Windows 平台 PE(Portable Executable)文件静态分析工具,作为经典工具 PEiD 的升级替代版,广泛用于逆向工程、恶意软件分析、软件安全审计领域&#xf…

方达炬   发明一例新字词:地磁铁路

方达炬 发明一例新字词:地磁铁路

2026/7/18 17:03:32

方达炬 发明一例新字词:地磁铁路

ComfyUI图像生成自动化:n8n工作流实践指南

ComfyUI图像生成自动化:n8n工作流实践指南

2026/7/18 17:03:32

1. 为什么需要自动化ComfyUI图像生成流程? 在数字艺术创作和AI图像生成领域,ComfyUI作为基于节点的工作流工具,为创作者提供了极大的灵活性。但每次手动调整参数、点击生成按钮、保存结果这一系列重复操作,不仅效率低下&#xff0…

揭秘!哪家文化展厅专业又口碑好?

揭秘!哪家文化展厅专业又口碑好?

2026/7/18 17:03:32

在当今文化产业蓬勃发展的时代,文化展厅作为文化传播与展示的重要载体,其建设质量与技术水平愈发受到关注。然而,当前文化展厅领域面临着诸多技术挑战,一定程度上制约了行业的发展。行业痛点分析传统广告加工商的固定工时运营模式…

无需复杂搭建:用文档提取+大模型快速构建政府文件智能分析助手

无需复杂搭建:用文档提取+大模型快速构建政府文件智能分析助手

2026/7/18 17:03:32

一、引言:跳过繁琐的智能体搭建您提出的思路非常可行且高效!确实可以跳过复杂的智能体框架搭建流程,通过“文档提取 大模型处理”的简单组合,快速实现一个能够搜集政府文件并总结分类的实用工具。这种方法的核心在于将任务拆解为…

CSS移动端开发基础(一):屏幕、像素、视口与适配方案全解析

CSS移动端开发基础(一):屏幕、像素、视口与适配方案全解析

2026/7/18 17:03:32

一、屏幕相关概念1.1 屏幕大小屏幕大小指屏幕的对角线长度,单位是英寸(inch)。常用尺寸说明3.5寸、4.7寸、5.0寸常见手机屏幕尺寸5.5寸、6.0寸、6.5寸大屏手机尺寸换算:1英寸(inch) 2.54厘米(cm…

LangGraph.js多Agent系统开发:从工作流原理到企业级实践

LangGraph.js多Agent系统开发:从工作流原理到企业级实践

2026/7/18 16:53:32

如果你正在构建复杂的AI应用,可能会遇到这样的困境:单个AI模型处理复杂任务时表现不稳定,而多个AI模型协作又难以管理。这正是LangGraph.js要解决的核心问题——它不是一个简单的AI工具库,而是专门为构建企业级多Agent系统设计的框…

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践

2026/7/18 14:07:00

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator XUnity.AutoTranslator作为Unity游戏社区中最成熟的文本翻译解决方…

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持

2026/7/17 17:34:09

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持 【免费下载链接】raspberrypi-kernel It provides openEuler kernel source for Raspberry Pi 项目地址: https://gitcode.com/openeuler/raspberrypi-kernel 前往项目官网免费下载&…

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧

2026/7/18 5:51:23

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧 【免费下载链接】integration-test The repo contains test suits for system integration test 项目地址: https://gitcode.com/openeuler/integration-test 前往项目官网免费下载:…

从模糊意图到可执行指令:Claude PRD中Prompt Engineering与需求颗粒度的5级映射法则

从模糊意图到可执行指令:Claude PRD中Prompt Engineering与需求颗粒度的5级映射法则

2026/7/18 0:02:02

更多请点击: https://kaifayun.com 第一章:从模糊意图到可执行指令:Claude PRD中Prompt Engineering与需求颗粒度的5级映射法则 在Claude驱动的产品需求文档(PRD)生成实践中,原始业务意图往往以自然语言片…

Cursor配置生成失效?3大隐藏陷阱+4行修复代码,资深工程师连夜整理的紧急补救清单

Cursor配置生成失效?3大隐藏陷阱+4行修复代码,资深工程师连夜整理的紧急补救清单

2026/7/18 0:02:02

更多请点击: https://codechina.net 第一章:Cursor配置生成失效?3大隐藏陷阱4行修复代码,资深工程师连夜整理的紧急补救清单 Cursor 配置生成突然失效,是近期高频报障场景。表面看是 cursor.config.json 未更新或 LSP…

某智驾大牛创业

某智驾大牛创业

2026/7/18 0:02:02

作者:钟声编辑:Mark出品:红色星际头图:智能驾驶图片据悉,国内某头部智驾公司端到端模型技术大牛Z投身创业,并且已经拿到融资。Z不仅是该头部公司内部最年轻的对标阿里P10级别技术负责⼈,更是业内…