单元测试是回归防护网的最基础层。本页从”为什么需要测试”出发,梳理测试金字塔、主流框架横评、AAA 写法、TDD/BDD 哲学,以及 Jest 在 RN 项目中的配置决策。重点说明为什么 Jest + jest-expo 在 RN 生态里压过 Vitest,即便后者速度更快。
概述:测试的价值
代码改动后最可怕的不是”改错了”,而是”不知道改错了没”。没有自动化测试,你只能手动在模拟器上点一遍。漏一个场景、改动传导到意想不到的模块——都是常见翻车现场。
自动化测试的本质是把”回归验证”从手动几十分钟变成自动几秒钟。这个循环越短,重构就越敢做,架构就越能保持健康。反过来,没有测试的项目会悄悄腐化:你怕改东西,于是宁可复制粘贴也不抽象。
工作方式:测试金字塔
测试按抽象层级分三类:
/\
/E2E\ 少量:端到端,模拟用户完整操作
/────\ (Maestro、Playwright、Detox)
/ 集成 \ 中量:多模块协作
/────────\ (API + DB、Hook + Store)
/ 单元测试 \ 大量:纯函数逻辑
/────────────\ (Jest、Vitest、Mocha)
| 类型 | 覆盖什么 | 速度 | 建议比例 |
|---|---|---|---|
| 单元测试 | 单个函数/方法,隔离依赖 | 毫秒级 | 70-80% |
| 集成测试 | 多模块真实协作 | 秒级 | 15-25% |
| E2E 测试 | 用户视角的完整流程 | 分钟级 | 5-10% |
原则:能用下层测试覆盖的,不要用上层测试覆盖——下层快、定位精准、成本低。
框架横评
四大候选
| 维度 | Jest | Vitest | Mocha | Ava |
|---|---|---|---|---|
| 哲学 | all-in-one | all-in-one + Vite 速度 | 极简,组合其他 | 并发 + 函数式 |
| TS 支持 | 需 babel-jest / ts-jest | 原生 | 需额外包 | 原生 |
| ESM 支持 | 实验性 | 原生 | 需配置 | 原生 |
| 速度 | 慢(Node CJS) | 快(Vite HMR) | 中 | 快(并发) |
| RN 生态 | ⭐ 官方(jest-expo) | ⚠️ 社区方案 | ⚠️ 少 | ⚠️ 少 |
| 社区规模 | 最大 | 快速增长 | 成熟但老派 | 小众 |
RN 项目的决策:Jest + jest-expo
Jest 在 RN 场景的压倒性优势:
- Expo 官方 create-expo-app 模板默认用
jest-expo—— 最少阻力的路径 @testing-library/react-native的官方文档只给 Jest 示例 —— 遇到问题能找到答案react-native-mmkv提供的/mock工具是 Jest-first —— 持久化测试零配置jest-expo预置了所有 Expo 模块的 mock(expo-router、expo-constants、expo-localization 等)- 面试价值:RN 业界 90%+ 项目用 Jest,面试官默认你熟悉它
Vitest 仍然有价值的场景:
- 纯 TS 工具库(无 RN native 依赖)
- 负向类型测试(
@ts-expect-error验证缺 key 编译失败) - monorepo 的 shared 包
关键洞察:Jest 和 Vitest 的 API 几乎一致——describe / it / expect / beforeEach 写法相同。这是 Vitest 刻意设计的兼容层。“框架选择”主要是工具链和生态的选择,不是 API 的选择。
AAA 模式(Arrange-Act-Assert)
写测试的金标准模式,每个用例分三段:
describe('add', () => {
it('两个正数相加应返回它们的和', () => {
// Arrange — 准备数据
const a = 2
const b = 3
// Act — 调用被测函数
const result = add(a, b)
// Assert — 验证结果
expect(result).toBe(5)
})
})
为什么重要:
- 可读性:30 秒内看懂测什么
- 调试性:失败时能定位是数据/调用/断言哪步错
- 一致性:团队风格统一
常见反模式:
- AAA 混成一行(
expect(add(2, 3)).toBe(5))——读起来要倒推 - 一个
it测多件事——某一项失败时其他结果被遮蔽
原则:一个 it 只测一件事,测试名描述行为不是函数名。
TDD vs BDD
TDD(Test-Driven Development)
Kent Beck 提出,先写测试再写代码:
RED → GREEN → REFACTOR
- RED:写一个失败的测试,定义需求和成功标准
- GREEN:写最少的代码让测试通过(不追求完美)
- REFACTOR:在测试保护下重构,质量提升
价值:保证代码可测试、防止过度设计、覆盖率自然高。
代价:节奏慢、对需求清晰度要求高、不适合探索型开发。
BDD(Behavior-Driven Development)
Dan North 提出,TDD 的演进:不测”函数做了什么”,测”用户能观察到什么”。
# TDD 风格
describe('add', () => {
it('returns sum of two numbers', () => {})
})
# BDD 风格
describe('两个数相加', () => {
it('用户输入两个正数时,得到它们的和', () => {})
})
实践建议
Lite TDD + BDD 风格命名:
- 不强制先写测试再写代码
- 对关键基础设施(类型系统、状态管理、持久化层)做 RED/GREEN 循环
- 测试名用人类语言描述行为,不是 API 名
Jest 配置要点(jest.config.js)
preset: ‘jest-expo’
Expo 官方预设,做了以下事:
- 配置 babel transform 让 Jest 理解 TS/JSX
- 预先 mock Expo 模块(expo-constants、expo-router 等)
- 预先 mock RN 内置模块(Animated、NativeModules、Platform)
- 设置 jsdom + react-native 的测试环境
不用 preset 就要手写大量 moduleNameMapper 和 transform 规则。
setupFilesAfterEach
每个测试文件加载后执行的 setup。引入 @testing-library/jest-native/extend-expect 后可以用语义化 matcher:
// 不装:expect(node).toBeTruthy()
// 装了:expect(node).toBeVisible()
// 装了:expect(input).toHaveProp('placeholder', '请输入')
moduleNameMapper
Jest 不读 tsconfig.json 里的 paths。如果 TS 代码用 import foo from '@/store/settings',必须显式告诉 Jest:
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
}
transformIgnorePatterns
默认 Jest 不转换 node_modules(速度考量),但 RN 生态的 nativewind、@react-native、expo-* 发布的是 ESM 源码,Node 不懂,必须经过 Babel。
配置在 jest-expo 默认值上扩展,明确列出需要转换的包。pnpm 用户还要匹配 .pnpm/ 路径。
Jest 常用 API 速查
生命周期
beforeAll() // suite 前一次
beforeEach() // 每个 it 前
afterEach() // 每个 it 后
afterAll() // suite 后一次
断言 matcher
expect(v).toBe(5) // === 原始值
expect(obj).toEqual({ a: 1 }) // 深度相等
expect(arr).toContain(3) // 数组包含
expect(str).toMatch(/regex/) // 正则
expect(fn).toThrow('msg') // 异常
expect(promise).resolves.toBe(v) // async
Mock
const fn = jest.fn(() => 'x') // mock 函数
jest.mock('@/path') // mock 模块
jest.spyOn(obj, 'method') // spy
参见
- 2026-04-13-Flexbox 与 RN 布局 — RN 布局基础
- 2026-04-13-NativeWind 与 Tailwind 设计哲学 — NativeWind 编译原理
- 2026-04-13-React Native 新架构概览 — Fabric / JSI / TurboModules
参考
- Jest 官方文档 — 权威参考
- jest-expo 源码 — 理解 preset 做了什么
- Testing Library 哲学 — 为什么”测行为不测实现”
- Martin Fowler: UnitTest — 经典定义
- Kent C. Dodds: Common mistakes with React Testing Library — 实战常见错误