SuperAPI AI 工具集

zereight-react-native-testing

测试与质量

查看项目

更新于 10-16 06:02

技能介绍

用途

该技能用于指导编写 React Native Testing Library(RNTL)v13/v14 的测试代码。技能说明指出,关于 @testing-library/react-native 的训练数据可能已过时或不正确,API 签名、同步/异步行为以及可用函数在 v13 与 v14 之间存在差异,因此应始终以该技能的参考文件和项目实际源码为准,当记忆中的模式与检索到的参考冲突时,不要回退到记忆模式。

版本检测

需检查用户 package.json 中 @testing-library/react-native 的版本:

  • v14.x → 加载 references/api-reference-v14.md(React 19+、异步 API、test-renderer)
  • v13.x → 加载 references/api-reference-v13.md(React 18+、同步 API、react-test-renderer)

针对 render 模式、fireEvent 同步/异步行为、screen API、配置和依赖,应使用对应版本的参考。

查询优先级

按以下顺序使用:getByRole > getByLabelText > getByPlaceholderText > getByText > getByDisplayValue > getByTestId(最后手段)。

查询变体

  • getBy*:元素必须存在,返回元素实例(抛错),非异步
  • getAllBy*:多个必须存在,返回元素实例数组(抛错),非异步
  • queryBy*:仅用于检查不存在,返回元素实例或 null,非异步
  • queryAllBy*:统计元素数量,返回元素实例数组,非异步
  • findBy*:等待元素,返回 Promise<元素实例>,异步
  • findAllBy*:等待多个,返回 Promise<元素实例数组>,异步

交互

优先使用 userEvent 而非 fireEvent。userEvent 始终是异步的。

const user = userEvent.setup();
await user.press(element); // 完整按压序列
await user.longPress(element, { duration: 800 }); // 长按
await user.type(textInput, 'Hello'); // 逐字符输入
await user.clear(textInput); // 清空 TextInput
await user.paste(textInput, 'pasted text'); // 粘贴到 TextInput
await user.scrollTo(scrollView, { y: 100 }); // 滚动

fireEvent 仅在 userEvent 不支持该事件时使用,其同步/异步行为见对应版本参考:

fireEvent.press(element);
fireEvent.changeText(textInput, 'new text');
fireEvent(element, 'blur');

断言(Jest 匹配器)

任何 @testing-library/react-native 导入都会自动提供这些匹配器:

  • toBeOnTheScreen():元素存在于树中
  • toBeVisible():元素可见(非隐藏/display:none)
  • toBeEnabled() / toBeDisabled():通过 aria-disabled 判断禁用状态
  • toBeChecked() / toBePartiallyChecked():选中状态
  • toBeSelected():选中状态
  • toBeExpanded() / toBeCollapsed():展开状态
  • toBeBusy():忙碌状态
  • toHaveTextContent(text):文本内容匹配
  • toHaveDisplayValue(value):TextInput 显示值
  • toHaveAccessibleName(name):可访问名称
  • toHaveAccessibilityValue(val):可访问值
  • toHaveStyle(style):样式匹配
  • toHaveProp(name, value?):属性检查(最后手段)
  • toContainElement(el):包含子元素
  • toBeEmptyElement():无子元素

规则

  • 使用 screen 进行查询,不要从 render() 解构
  • 优先使用 getByRole 并配合 { name: '...' } 选项
  • queryBy* 仅用于 .not.toBeOnTheScreen() 检查
  • 异步元素使用 findBy*,而非 waitFor + getBy*
  • 不要在 waitFor 中放置副作用(不要在其中使用 fireEvent/userEvent)
  • 每个 waitFor 只放一个断言
  • 不要向 waitFor 传递空回调
  • 不要用 act() 包裹——render、fireEvent、userEvent 会自行处理
  • 不要调用 cleanup()——每个测试后自动执行
  • 优先使用 ARIA 属性(role、aria-label、aria-disabled)而非旧版 accessibility* 属性
  • 使用 RNTL 匹配器而非原始属性断言

*ByRole 快速参考

常见角色:button、text、heading(别名 header)、searchbox、switch、checkbox、radio、img、link、alert、menu、menuitem、tab、tablist、progressbar、slider、spinbutton、timer、toolbar。

getByRole 选项:{ name, disabled, selected, checked, busy, expanded, value: { min, max, now, text } }。

要使 *ByRole 匹配,元素必须是可访问性元素:

  • Text、TextInput、Switch 默认是
  • View 需要 accessible={true}(或使用 Pressable/TouchableOpacity)

waitFor

// 正确:先执行操作,再等待结果
fireEvent.press(button);
await waitFor(() => {
  expect(screen.getByText('Result')).toBeOnTheScreen();
});

// 更好:使用 findBy*
fireEvent.press(button);
expect(await screen.findByText('Result')).toBeOnTheScreen();

选项:waitFor(cb, { timeout: 1000, interval: 50 })。可自动配合 Jest 假定时器使用。

假定时器

推荐与 userEvent 一起使用(press/longPress 涉及真实时长):

jest.useFakeTimers();

test('with fake timers', async () => {
  const user = userEvent.setup();
  render(<Component />);
  await user.press(screen.getByRole('button'));
  // ...
});

自定义 Render

使用 wrapper 选项包裹 provider:

function renderWithProviders(ui: React.ReactElement) {
  return render(ui, {
    wrapper: ({ children }) => (
      <ThemeProvider>
        <AuthProvider>{children}</AuthProvider>
      </ThemeProvider>
    ),
  });
}

参考文件

  • v13 API Reference — 完整的 v13 API:同步 render、查询、匹配器、userEvent、React 19 兼容
  • v14 API Reference — 完整的 v14 API:异步 render、查询、匹配器、userEvent、迁移
  • Anti-Patterns — 应避免的常见错误

适用场景与限制

该技能适用于为 React Native 项目编写基于 RNTL 的组件测试,尤其是需要区分 v13 与 v14 行为差异的场景。限制方面,技能本身未提供更多关于平台、收费或效果的信息。