您的 React Native 测试套件是绿色的,但用户仍然报告说在真实设备上点击“继续”什么也没有发生。组件测试找到了按钮,调用了其处理程序,并在模拟 JavaScript 环境中看到预期屏幕。它从未验证原生权限提示、键盘行为、动画、平台API或实际导航堆栈。
那就是团队获得错误信心的地方。 React Native Testing Library 用于测试组件的渲染和对用户交互的响应,但它并不是设备测试或性能测量的替代品。可靠的策略是使用每层级别来暴露它可以暴露的失败。
目录
为什么用户中心测试改变了一切
一个常见的测试故障始于测试写之前。开发人员检查组件的属性,进入其状态,或者比较一个大型快照,因为这些细节很容易断言。测试通过,然后重构改变了组件结构而没有改变体验,测试套件就破裂了。更糟糕的是,测试可以继续通过,而用户可见的行为是错误的,因为断言从未描述过用户需要看到的内容。
React Native Testing Library采取了相反的方法。您渲染组件,通过可见的控件与其交互,然后断言用户可以观察到的结果。这种方法与 React Native Testing Library的用户中心示例相匹配, as well as React Native’s testing guidance to keep tests short, focus each test on one thing, separate view concerns from business logic and state, and prefer visible output or accessibility helpers over internal implementation details.

测试用户依赖的行为
假设登录表单在请求期间禁用其提交按钮。一个脆弱的测试可能会检查 disabled 在特定组件实例上或断言状态变量发生了变化。一个更强大的测试会按下可访问的“登录”控件,等待加载指示器,并检查错误消息或目的地屏幕出现。
第二个测试并不关心表单是否使用本地状态、减少器、自定义钩子或不同的按钮实现。它关心的是应用程序传达正确的结果。
实践规则: 如果用户无法观察到它,是否应该在组件行为测试中包含它就值得怀疑。
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 更好地与辅助技术一起使用并更容易在测试中使用。这种联系在评估更广泛的应用程序用户体验时很重要,因为可测试性和可用性往往会一起改善。
了解什么是通过测试的测试不能证明
A用户中心的组件测试可以证明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预设,而裸露的React Native项目可能使用React Native预设:
npm install --save-dev @testing-library/react-native jest
对于Expo,添加项目已经使用的预设:
{
"scripts": {
"test": "jest"
},
"jest": {
"preset": "jest-expo"
}
}
对于一个裸露的 React Native 应用程序,使用相应的 React Native Jest 配置。保持预设与框架版本对齐。许多看起来像 RNTL 问题的失败都是由于匹配不上的 React 渲染器、Babel 转换或 Jest 预设而导致的。
保持设置文件明确
将共享环境配置放在设置文件中,而不是在每个测试中重复模拟:
// 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 和 .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单元测试指南,然后在添加导航和原生模拟之前运行一个小组件测试

核心API、查询和断言的解释
RNTL测试只有当它们的查询与用户可以看到、找到和操作的内容匹配时才会可信赖。API很小,但选择一个暴露实现细节的选择器会使一个通过的测试变得误导
从 render:
const screen = render(<LoginForm />);
选择最相关的用户查询。优先使用可访问性导向的查询,当组件暴露它们时使用可见文本,当文本是行为时保留 testID 对于没有可用用户界面选择器或需要稳定的集成钩子时的案例。

根据时间和意图选择查询
每个查询家族都有一个独特的目的:
- 同步存在: 使用
getByRole,getByText或另一个getBy查询,当元素应该已经存在时。测试立即失败如果它不。 - 异步出现: 使用
findByRole或findByTextWhen rendering or interaction causes an async update. - 缺失检查: 使用
queryByText或queryByTestId在渲染或交互导致的异步更新时使用。 - 在元素可能缺失时,期望返回 null 而不是抛出异常。 回退选择器:
getByTestId使用
故意使用。它为复杂控件提供了一个可靠的钩子,但不应替代整个应用程序的可访问标签。 fireEvent.press 对于聚焦事件测试来说,
fireEvent.press(screen.getByRole('button', { name: 'Save' }));
是直接的: userEvent 使用
expect(await screen.findByText('Saved')).toBeTruthy();
A handler assertion适用于其公共契约为事件回调的组件。它是弱的,因为只有用户旅程工作的证据。
优先使用具体的断言而不是快照
好的断言描述屏幕:
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 __CAPGO_KEEP_0__ 和一个移动应用程序模拟。

保持一个组件测试与其公共契约接近:
确保标签的准确性与 UI 一致。 重要的是测试找到控制的方式与用户或辅助技术服务一样,并确认可见或回调结果的重要部分。 不要默认地模拟每个子组件。 只有在它们模糊测试行为时才模拟昂贵或不相关的边界。
const onSelect = jest.fn();
render(
<PlanCard
title="Team"
description="Shared workspace"
onSelect={onSelect}
/>
);
fireEvent.press(screen.getByRole('button', { name: 'Choose Team' }));
expect(onSelect).toHaveBeenCalled();
对于一个自定义钩子,使用
当安装的 RNTL 版本提供它时: renderHook react-native-testing-library
const { result } = renderHook(() => useSearch());
await act(async () => {
await result.current.submit('query');
});
expect(result.current.status).toBe('success');
测试钩子应该控制网络或仓库边界,而不是重现整个应用。测试屏幕单独,以便您知道钩子的状态是否有用。
导航和异步数据
对于导航行为,渲染一个真实的屏幕和一个小型测试导航器往往比每个导航方法都进行模拟更有价值。点击可见的控件,等待目的地内容,断言新屏幕的输出。直接 NavigationContainer 模拟对于一个小型按钮来说仍然合适,因为它的唯一职责就是分发一个类型化的路由,但它不会验证路由注册、参数或嵌套导航器行为。 useNavigation 异步数据 deserves 同样严格的纪律。模拟 __CAPGO_KEEP_0__ 或仓库响应,渲染屏幕,断言加载状态,解决请求,然后断言成功或错误输出。使用
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 验证 | 需要E2E验证 |
|---|---|---|
| 表单验证和可见错误 | 是 | 通常不是 |
| 从模拟仓库中获取的加载、成功和错误 UI | 是 | 对于关键生产流程 |
| 已注册屏幕之间的导航 | 是,带有测试导航器 | 是,当手势、深链接或平台行为很重要时 |
| AsyncStorage 状态决策 | 是,带有控制的模拟 | 是,当启动和持久性与原生生命周期相互作用时 |
| 摄像头、生物识别、权限或平台API | JS fallback和branching逻辑 | 是的,在真实或代表性设备上 |
| 布局、渲染性能和本机code | 否 | 是,使用设备或专用工具 |
边界是实际的:模拟依赖项以测试JavaScript决策,然后在确认依赖项的真实行为时运行设备测试。
调试易碎测试CI和性能检查
易碎测试通常指出未控制的时间、共享状态或UI与断言竞争的条件。识别哪种情况存在之前不要增加超时。更长的超时可能会掩盖调度问题并使套件变慢。
使用 findBy 用于预期在更新后出现的元素。使用 waitFor 用于状态条件或模拟调用。如果启用了虚拟时钟,则在交互所需的点上推进它们,并在之后恢复真实时钟。 act 警告意味着 React 观察到一个更新在其预期的交互边界之外。修复缺失的 await用户交互或定时器刷新,而不是抑制警告。
使 CI 失败可复现
可靠的 CI 任务从锁文件安装,运行本地使用的相同 Jest 命令,并隔离模拟状态。清除模拟调用之间的测试,重置模块,当模块级别的状态影响行为时,重置模块,并移除依赖于执行顺序的依赖项。Jest 缓存加速反馈,但依赖项或配置更改需要适当的缓存失效。
CI-only 失败需要在组件重写之前进行环境比较。检查 Node、包管理器、Jest 工作程序设置、定时器配置和环境变量。尽可能地在本地复现相同的命令,然后将失败的测试缩小到最小的交互以暴露差异。
可观察性覆盖了一个不同的缺口。工具,如 React Native 的 Sentry 供应生产错误上下文,模拟组件测试无法复制的内容,包括与真实设备和本机集成相关的失败。
安全敏感的旅程需要第二个边界。使用组件测试进行验证和状态转换,然后添加设备级别的覆盖以测试手动和本机行为。对于一次性code流程,请参阅有关 测试 SMS 验证流程 当该集成形成旅程的一部分时。
将性能视为测量,而不是断言
A功能性测试可以确认列表渲染。它无法可靠地确定重构是否改变了渲染时间或渲染次数,因为测试运行时会添加噪声。为场景测量这些值,重复场景以减少方差,并在报告有意义的变化之前应用统计分析。它 性能测试文档 还涵盖了适合CI和pull请求审查的报告。
避免使用固定的断言,如“渲染必须在选择的阈值以下完成。”忙碌的CI工人可以触发假失败,而松散的阈值可以错过真正的回归。使用重复测量来获取回归信号,然后在比较显示有意义差异时检查组件和设备配置。
测量规则: 功能性测试回答行为是否正确。性能工具回答是否测量场景发生了变化。将这些问题分开。
将所有内容放在一起并向前推进
React Native Testing Library属于测试策略的广泛、快速层。它应该涵盖组件行为、可见状态变化、可访问性结果、验证、模拟数据状态和JavaScript组件之间的集成。将这些测试保持在用户可以观察到的范围内,并使失败指向特定的行为而不是大型渲染树。
较小的设备层应该保护流程,避免在那里使用mock。React Native的 测试概述 指出RNTL并未提供完整的React Native运行时,并且无法测试原生功能。同样的指引建议将组件测试与E2E工具如Detox结合使用,用于关键流程,包括身份验证、支付和核心应用功能。
实用迁移路径
您不需要一次性重写现有套件。
- 保留有价值的业务测试。 将纯粹的状态和域逻辑移到专注的单元测试中,提供清晰的反馈。
- 首先替换实现断言。 将属性和内部状态检查转换为可见的输出、可访问性状态和交互结果。
- 缩小快照。 只保留审阅者可以理解和维护的快照。
- 添加原生边界覆盖。 对于每个重要的模拟模块,确定仍然需要验证的设备行为。
- 保护关键旅程。 为认证、支付、核心导航、权限和其他可能受原生行为影响的流程添加E2E覆盖。
- 单独测量敏感屏幕。 使用重复的性能比较来代替Jest持续时间猜测,用于列表、feed和昂贵的渲染路径。
为发布信心,连接套件到 CI/CD集成测试 并在交付之前要求适当的测试层。如果在验证后必须将JavaScript或资产修复传递给用户,Capgo可以将签名的Web包传递给CapacitorJS和Electron应用的目标通道,具有滚动控制和回滚保护。该交付工作流程不会替代React Native设备测试,但它说明了同样的原则:在它运行的层面上验证行为。
正确的问题不是React Native Testing Library是否可以测试您的整个应用。它不能,官方边界是有用的。正确的问题是每个重要风险是否有一个在能够暴露它的环境中运行的测试。
Capgo将验证的JavaScript和资产更改连接到CapacitorJS和Electron团队的控制传递,具有目标通道、滚动可见性和回滚保护。访问 Capgo 查看它如何与您的组件、E2E和CI测试工作流程一起工作。