跳过主要内容

React Native测试库如何测试应用

掌握React Native测试库的设置、查询、模拟和CI技巧。为组件、钩子和导航构建可靠的用户中心测试。

React Native Testing Library 如何测试应用程序

您的 React Native 测试套件是绿色的,但用户仍然报告说在真实设备上点击“继续”时什么也没有发生。组件测试找到了按钮,调用了其处理程序,并在模拟 JavaScript 环境中看到预期屏幕。它从未验证原生权限提示、键盘行为、动画、平台 API 或实际导航堆栈。

那就是团队获得错误信心的地方。 React Native 测试库 用于测试组件渲染和响应用户交互的功能非常出色,但它并不是设备测试或性能测量的替代品。可靠的策略是使用每个层次来暴露它可以暴露的失败。

目录

为什么用户中心测试会改变一切

A测试通常在测试写好之前就失败了。开发者检查组件的属性,进入其状态,或者比较一个大型快照,因为这些细节很容易断言。测试通过,然后重构改变了组件结构而没有改变体验,测试套件就崩溃了。更糟糕的是,测试可以继续通过,而用户可见的行为是错误的,因为断言从未描述过用户需要看到什么。

React Native Testing Library采取了相反的方法。您渲染组件,通过可见的控件与其交互,然后断言用户可以观察到的结果。这种方法与 React Native Testing Library的用户中心的示例,以及React Native的测试指导,建议测试短小精悍,每个测试关注一个问题,分离视图关注点与业务逻辑和状态,优先使用可见输出或辅助性帮助而不是内部实现细节。

用户行为、韧性、可靠性和更好的实践的图表,突出了用户行为的好处。

测试用户依赖的行为

假设一个登录表单在请求运行时禁用其提交按钮。脆弱的测试可能会检查 disabled 某个组件实例或断言状态变量发生了变化。更强大的测试会按下可访问的“登录”控件,等待加载指示器,然后检查错误消息或目的地屏幕是否出现。

第二个测试不关心表单是否使用本地状态、reducer、自定义钩子或不同的按钮实现。它关心的是应用程序传达正确的结果。

实用规则: 如果用户无法观察到它,是否该行为属于组件行为测试中就值得怀疑。

Queries based on accessibility labels and roles also force better product code. A screen that exposes meaningful labels is easier to use with assistive technology and easier to exercise in tests. That connection matters when you assess the broader 应用程序用户体验时很重要,因为可测试性和可用性经常会一起改进。

了解什么是通过测试无法证明的

用户中心的组件测试可以证明JavaScript渲染了预期的 branch 并对模拟的按压做出响应。它无法证明生物识别提示打开正确、原生摄像头返回可用的结果或支付流程在平台特定的生命周期行为下存活。

这种界限不是库的弱点。它是测试层保持诚实的原因。使用RNTL进行组件行为,然后将设备测试保留用于流程,其中原生集成、真实导航、权限、身份验证、支付或核心应用程序功能可以改变结果。

安装和配置确实有效

现代设置从scoped包开始:

@testing-library/react-native

较旧 react-native-testing-library npm 包名仍然作为历史文档存在,但当前项目应使用 Testing Library 家族内维护的scoped包。该项目的 GitHub 仓库 描述了它作为 React Native 的工具,鼓励良好的测试实践,并且其发布历史表明该库一直在随着 React Native 的变化而变化,而不是保持静态的辅助工具。

使用 Jest 环境安装该库。Expo 项目通常使用 Expo Jest preset,而裸露的 React Native 项目可能使用 React Native preset:

npm install --save-dev @testing-library/react-native jest

对于 Expo,添加项目已经使用的 preset:

{
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "preset": "jest-expo"
  }
}

对于裸露的 React Native 应用程序,使用相应的 React Native Jest 配置。保持 preset 与框架版本一致。许多看起来像 RNTL 问题的失败都是由于匹配不上的 React 渲染器、Babel 转换或 Jest preset 引起的。

保持设置文件明确

将共享环境配置放在设置文件中,而不是在每个测试中重复模拟:

// jest.setup.js
import '@testing-library/react-native/extend-expect';

然后从 Jest 引用它:

{
  "jest": {
    "preset": "jest-expo",
    "setupFilesAfterEnv": ["<rootDir>/jest.setup.js"]
  }
}

如果您的应用程序使用 React Navigation、Reanimated、手势处理、安全区域上下文或存储模块,请仅配置测试环境需要的模拟。一个全局模拟会改变应用程序行为,使每个测试更容易通过,但使测试套件更不可信。

TypeScript 需要同样的关注。确保 Jest 转换 .ts 并且(And) .tsx 通过预设或您的Babel配置文件,保留测试类型供编译器使用。一个本地运行但未进行类型检查的测试可能会隐藏错误的查询名称、无效的导航参数或不安全的模拟形状。

发布时间线在诊断旧的设置建议时很有用。项目列表 127个发布,其中 v14.0.1标记于2026-06-23,而 v12.9.0,于2024-11-27发布,添加了对React Native 0.77和Expo 52的官方支持。. 2025年3月的v14 alpha线从已弃用的React Test Renderer转移到Universal Test Renderer,并为React 19-only支持做好准备。这些细节出现在 RNTL发布历史中,所以不要从过时的教程中复制一个渲染器依赖项,而不检查您的应用程序的版本。

为了实用性的Jest基础,比较您的配置与此 Jest单元测试指南然后再添加导航和原生模拟器之前,运行一个小型组件测试。

设置 React Native Testing Library 项目的五步安装路径的 infographic 图。

Core APIs Queries 和 Assertions 解释

当 RNTL 测试的查询匹配用户可以看到、找到和操作的内容时,测试才会变得可信赖。API 的大小很小,但选择暴露实现细节的选择器会使一个通过的测试变得误导性。

从这里开始 render:

const screen = render(<LoginForm />);

选择最相关的用户查询。优先使用可访问性导向的查询,当组件暴露它们时使用可见文本,当文本是行为时使用它,并在没有意义的用户界面选择器或需要稳定的集成钩子时保留。 testID 软件测试自动化中推荐的查询优先顺序的 infographic 金字塔图。

根据时间和意图选择查询

每个查询家族都有一个独特的目的:

同步存在:

  • 使用 同步存在: getByRole, getByText, 或者另一个 getBy 当元素已经存在时,查询。测试如果它不存在立即失败。
  • 异步出现: 使用 findByRole 或 findByText 当渲染或交互导致异步更新时使用。
  • 缺失检查: 使用 queryByText 或 queryByTestId 当元素可能不存在并期望 null 结果而不是异常时使用。
  • 回退选择器: 使用 getByTestId 故意地。它为复杂控件提供了一个可靠的钩子,但不应替代整个应用程序的可访问标签。

对于专注于事件测试的 fireEvent.press 是直接的:

fireEvent.press(screen.getByRole('button', { name: 'Save' }));

使用 userEvent 当安装的版本支持更现实的交互序列时。无论哪种方式,断言结果的UI:

expect(await screen.findByText('Saved')).toBeTruthy();

一个处理程序断言适合一个公共契约是事件回调的组件。它是弱的,因为它是唯一的证据,表明用户旅程有效。

优先选择具体断言而不是快照

好的断言描述屏幕:

expect(screen.getByText('Account created')).toBeTruthy();
expect(screen.getByRole('button', { name: 'Continue' })).toBeEnabled();

它们还可以验证可访问性状态、选择和可见的验证反馈。避免检查内部连接:

expect(screen.getByTestId('submit-button').props.onPress).toBeDefined();

这个断言证明了一个属性存在,而不是该功能有效。小的有意的快照可以捕捉结构变化,而大型导航或屏幕快照通常会创建噪音的评论,并使未经解释的更新容易批准。

测试库包属于更广泛的 testing-library npm组织。其活跃包包括 @testing-library/react-native,版本 13.3.3于2026年发布,根据项目的 存储库信息。 共享查询约定在各个平台上有所帮助,但它们并不能决定哪个断言代表了您的产品行为。

为了更广泛地比较Jest组件测试实践,请参阅有关 单元测试React的指南。 RNTL仍然停在JavaScript渲染组件的边界上。 原生权限、真实的导航堆栈、设备键盘、帧定时和内存行为需要E2E或性能工具,而不是更多的组件模拟。

以下视频演示了在上下文中使用的查询和断言工作流。

组件、钩子和导航的实用模式

一个有用的测试套件遵循应用程序的形状。 表现性组件需要直接行为测试,钩子需要控制的输入和输出,导航需要一个足够现实的提供者,而原生模块需要模拟,这些模拟在真实设备验证中仍然可见。

一台现代笔记本电脑放在一张木桌子上,显示React Native code和一个移动应用程序的模拟。

功能组件

保持组件测试与其公共接口接近:

const onSelect = jest.fn();

render(
  <PlanCard
    title="Team"
    description="Shared workspace"
    onSelect={onSelect}
  />
);

fireEvent.press(screen.getByRole('button', { name: 'Choose Team' }));

expect(onSelect).toHaveBeenCalled();

确保标签精确匹配UI。重要的是测试找到控制器的方式与用户或辅助技术服务相同,并确认可见或回调结果。不要默认 mock 每个子组件。只有当它们遮挡测试行为时才 mock 贵重或无关的边界。

对于自定义钩子,使用 renderHook 当安装的RNTL版本提供时:

const { result } = renderHook(() => useSearch());

await act(async () => {
  await result.current.submit('query');
});

expect(result.current.status).toBe('success');

钩子测试应该控制网络或仓库边界,而不是重现整个应用。测试屏幕单独,以便您知道钩子的状态是否有用UI。

对于导航行为,渲染一个真实 NavigationContainer 和一个小的测试导航器通常比 mock 每个导航方法更有价值。按一个可见的控制器,等待目的地内容,断言新屏幕的输出。一个直接 useNavigation mock仍然适用于一个小按钮,其唯一责任是分发一个类型化的路由,但它不会验证路由注册、参数或嵌套导航器行为。

Async data deserves the same discipline. Mock the API or repository response, render the screen, assert the loading state, resolve the request, then assert success or error output. Use findBy 查询期望的最终元素,并使拒绝请求明确,否则测试可能通过,因为组件没有达到预期分支。

原生模块模拟

模拟AsyncStorage、权限、摄像头、生物识别和平台API的调用对于确定性的JavaScript测试非常有用。它们并不是原生功能工作的证据。请将模拟行为保持在模块协议附近,测试之间重置调用,并在模拟中包含失败响应,而不是仅仅模拟成功路径。

场景 最佳与RNTL 需要E2E验证
表单验证和可见错误 是 通常不需要
通常不 是 从模拟仓库加载的加载、成功和错误UI
对于关键生产流程 是的,使用测试导航器 是的,当手势、深度链接或平台行为很重要时
异步存储状态决策 是的,使用受控的模拟 是的,当启动和持久性与原生生命周期相互作用时
相机、生物识别、权限或平台API JS fallback和分支逻辑 是的,在真实设备或代表性设备上
布局、渲染性能和原生code 否 是的,使用设备或专门的工具

界限是实际的:模拟依赖项以测试JavaScript决策,然后在设备上运行以确认依赖项的真实行为

调试不稳定测试 CI 和性能检查

一个脆弱的测试通常指出未控制的时间、共享状态或 UI 与断言竞争。识别哪种情况存在之前提高超时。一个更长的超时可以隐藏调度问题并使套件变慢。

使用 findBy 对于预期在更新后出现的元素。使用 waitFor 对于状态条件或模拟调用。如果启用了虚拟时钟,务必要在交互所需的点上推进它们,并在之后恢复真实时钟。一个 act 警告意味着 React 观察到一个更新超出了预期的交互边界。修复缺失的 await用户交互或时钟刷新,而不是抑制警告。

使 CI 失败可复现

可靠的 CI 任务从锁文件安装,运行本地使用的相同 Jest 命令,并隔离模拟状态。清除模拟调用之间的测试,重置模块,当模块级别的状态影响行为时,重置模块,并移除依赖于执行顺序的依赖项。Jest 缓存加速反馈,但依赖项或配置更改需要适当的缓存失效。

CI-only 失败需要在组件重写之前进行环境比较。检查 Node、包管理器、Jest 工作程序设置、时钟配置和环境变量。尽可能地在本地复制相同的命令,然后将失败的测试缩小到最小的交互以暴露差异。

可观察性覆盖了一个不同的缺口。一个工具,如 Sentry for React Native 提供了生产错误的上下文,模拟组件测试无法复制的错误,包括与真实设备和本机集成相关的错误。

安全敏感的旅程需要第二个边界。使用组件测试进行验证和状态转换,然后添加设备级别的覆盖,用于手动和本机行为。对于一次性code流程,请参阅有关如何 测试短信验证流程 当该集成成为旅程的一部分时。

将性能视为测量,而不是断言

功能测试可以确认列表渲染。它无法可靠地确定是否重构改变了渲染时间或渲染次数,因为测试运行添加了噪声。通过为场景测量值、重复场景以减少方差并在报告有意义的变化之前应用统计分析来确保测量值。其 性能测试文档 还涵盖了适合CI和pull请求审查的报告。

避免固定的断言,如“渲染必须在选择的阈值以下完成。”忙碌的CI工人可以触发假失败,而松散的阈值可以错过真正的回归。使用重复测量来获取回归信号,然后在比较显示有意义差异时检查组件和设备配置文件。

测量规则: 功能测试回答行为是否正确。性能工具回答是否测量场景发生了变化。将这些问题分开。

将所有内容放在一起并向前推进

React Native 测试库属于测试策略的广泛、快速层。它应该涵盖组件行为、可见状态变化、可访问性结果、验证、模拟数据状态和 JavaScript 组件之间的集成。将那些测试聚焦在用户可以观察到的内容上,并让失败指向特定的行为,而不是一个大型渲染树。

React Native 的测试库应该保护在设备层面的小流程,防止 mock 值混入。 测试概述 实践的迁移路径

不需要一次性重写现有的测试套件。

保留有价值的商业测试。

  1. 将纯状态和域逻辑移到聚焦的单元测试中,提供清晰的反馈。 首先替换实现断言。
  2. 将属性和内部状态检查转换为可见输出、可访问性状态和交互结果。 缩小快照。
  3. 只保留审阅者可以理解和维护的快照。 A practical migration path
  4. 添加原生边界覆盖。 对于每个重要的模拟模块,确定设备行为仍需要验证的部分。
  5. 保护关键旅程。 为身份验证、支付、核心导航、权限和其他流程添加E2E覆盖,流程中原生行为可能会改变结果。
  6. 单独测量敏感屏幕。 使用重复的性能比较来测试列表、feed和昂贵的渲染路径,而不是根据Jest持续时间猜测。

为了确保发布的可靠性,将套件连接到 CI/CD集成测试 并在发布之前要求适当的测试层。如果需要将JavaScript或资产修复推送给用户,则Capgo可以将签名的Web包分发到CapacitorJS和Electron应用的目标渠道,具有滚动控制和回滚保护。该分发工作流程并不会取代React Native设备测试,但它说明了同样的原则:在行为运行的层面上验证行为。

正确的问题不是React Native Testing Library是否可以测试整个应用。它不能,官方边界是有用的。正确的问题是每个重要风险是否有一个在能够暴露它的环境中运行的测试。


Capgo将验证的JavaScript和资产更改连接到CapacitorJS和Electron团队的控制分发,具有目标渠道、滚动可见性和回滚保护。访问 Capgo 为了了解它如何与您的组件、E2E测试和CI测试工作流程一起工作。

Capacitor 应用实时更新

当 web 层 bug 实时更新时,通过 Capgo 发布修复,而不是等待几天的应用商店审批。用户在后台接收更新,而原生变化仍在正常审查路径中。

来自马丁的人性化支持

立即开始

最新博客文章

Capgo 为您提供创建真正专业的移动应用所需的最佳见解。