Skip to content
雲里
里雾

单元测试基础与 Jest 配置

mindgym 开发 更新于 2026/4/12

单元测试是回归防护网的最基础层。本页从”为什么需要测试”出发,梳理测试金字塔、主流框架横评、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%

原则:能用下层测试覆盖的,不要用上层测试覆盖——下层快、定位精准、成本低。


框架横评

四大候选

维度JestVitestMochaAva
哲学all-in-oneall-in-one + Vite 速度极简,组合其他并发 + 函数式
TS 支持需 babel-jest / ts-jest原生需额外包原生
ESM 支持实验性原生需配置原生
速度慢(Node CJS)快(Vite HMR)快(并发)
RN 生态⭐ 官方(jest-expo)⚠️ 社区方案⚠️ 少⚠️ 少
社区规模最大快速增长成熟但老派小众

RN 项目的决策:Jest + jest-expo

Jest 在 RN 场景的压倒性优势

  1. Expo 官方 create-expo-app 模板默认用 jest-expo —— 最少阻力的路径
  2. @testing-library/react-native 的官方文档只给 Jest 示例 —— 遇到问题能找到答案
  3. react-native-mmkv 提供的 /mock 工具是 Jest-first —— 持久化测试零配置
  4. jest-expo 预置了所有 Expo 模块的 mock(expo-router、expo-constants、expo-localization 等)
  5. 面试价值:RN 业界 90%+ 项目用 Jest,面试官默认你熟悉它

Vitest 仍然有价值的场景

关键洞察: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)
  })
})

为什么重要

常见反模式

原则:一个 it 只测一件事,测试名描述行为不是函数名。


TDD vs BDD

TDD(Test-Driven Development)

Kent Beck 提出,先写测试再写代码:

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 风格命名


Jest 配置要点(jest.config.js)

preset: ‘jest-expo’

Expo 官方预设,做了以下事:

  1. 配置 babel transform 让 Jest 理解 TS/JSX
  2. 预先 mock Expo 模块(expo-constants、expo-router 等)
  3. 预先 mock RN 内置模块(Animated、NativeModules、Platform)
  4. 设置 jsdom + react-native 的测试环境

不用 preset 就要手写大量 moduleNameMappertransform 规则。

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-nativeexpo-* 发布的是 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

参见


参考

  1. Jest 官方文档 — 权威参考
  2. jest-expo 源码 — 理解 preset 做了什么
  3. Testing Library 哲学 — 为什么”测行为不测实现”
  4. Martin Fowler: UnitTest — 经典定义
  5. Kent C. Dodds: Common mistakes with React Testing Library — 实战常见错误