使用 Jest 测试 React Native 应用:预设配置、快照测试与原生模块 Mock 实战指南
使用 Jest 测试 React Native 应用预设配置、快照测试与原生模块 Mock 实战指南【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest本文是 Jest 官方 React Native 测试指南对应仓库 website/versioned_docs/version-29.7/TutorialReactNative.md的完整实战化展开。FacebookMeta内部正是使用 Jest 来测试 React Native 应用本文将围绕如何让 Jest 与 React Native 协同工作这一核心主题带你完成从项目初始化、快照测试编写到transformIgnorePatterns等预设关键项调优再到用jest.mock屏蔽原生模块的完整链路。读完本文你将能够在自己的 React Native 工程中独立配置 Jest、编写并维护快照测试并从容处理第三方原生组件的测试隔离问题。一、SetupReact Native 工程中的 Jest 初始化从 react-native 0.38 版本开始执行react-native init时就会默认内置 Jest 配置。初始化完成后package.json中会自动出现如下配置v29 时代的写法配置放在package.json的jest字段中{ scripts: { test: jest }, jest: { preset: react-native } }之后只需执行yarn test或npm test即可用 Jest 运行测试。升级提示如果你的 React Native 应用是从旧版本升级而来且之前使用的是jest-react-native预设请从package.json中移除该依赖并把preset改为react-native。版本说明较新版本的 React Native 已将预设拆分为独立包react-native/jest-preset。如果jest.config.js中仍写preset: react-native且 Jest 报无法解析可安装react-native/jest-preset并改为preset: react-native/jest-preset。本仓库的官方示例 examples/react-native 完整演示了当前推荐形态——配置从package.json中迁出到独立的 jest.config.js并直接使用拆分后的预设包const {resolve} require(node:path); module.exports { preset: react-native/jest-preset, testEnvironmentOptions: { globalsCleanup: soft, }, // 该配置专属于 Jest 仓库自身一般项目无需添加 // 通常需要忽略的文件都在 node_modules 中默认已被忽略 transformIgnorePatterns: [resolve(__dirname, ../../packages)], };对应的 package.json 中测试脚本与依赖如下{ scripts: { start: react-native start, test: jest }, devDependencies: { react-native/jest-preset: 0.86.2, testing-library/react-native: ^14.0.1, babel-jest: workspace:*, jest: workspace:*, test-renderer: ^1.2.0 } }其中preset的解析与归一化由 packages/jest-config/src/normalize.ts 负责Jest 会读取preset指定的包或文件路径将其中的配置作为基底再与用户配置逐项合并用户配置始终拥有最终覆盖权。二、Snapshot Test用快照守护 React Native 组件快照测试Snapshot Testing是 React Native 场景下最常用的 Jest 能力之一。关于快照的通用机制可进一步阅读 docs/SnapshotTesting.md。2.1 待测组件先创建一个包含几个 View、Text 组件和样式的简单介绍组件与 examples/react-native/Intro.js 一致import React, {Component} from react; import {StyleSheet, Text, View} from react-native; class Intro extends Component { render() { return ( View style{styles.container} Text style{styles.welcome}Welcome to React Native!/Text Text style{styles.instructions} This is a React Native snapshot test. /Text /View ); } } const styles StyleSheet.create({ container: { alignItems: center, backgroundColor: #F5FCFF, flex: 1, justifyContent: center, }, instructions: { color: #333333, marginBottom: 5, textAlign: center, }, welcome: { fontSize: 20, margin: 10, textAlign: center, }, }); export default Intro;2.2 渲染与快照断言借助testing-library/react-native的render渲染组件再用 Jest 的toMatchSnapshot()将渲染结果保存为快照文件。testing-library/react-native取代了现已弃用的react-test-renderer需要同时安装其 peer dependencytest-rendereryarn add --dev testing-library/react-native test-rendererimport {render, screen} from testing-library/react-native; import React from react; import Intro from ../Intro; test(renders correctly, async () { await render(Intro /); expect(screen.toJSON()).toMatchSnapshot(); });首次运行yarn test或直接jest时Jest 会在__tests__/__snapshots__/目录下生成.snap快照文件例如__tests__/__snapshots__/Intro-test.js.snapexports[Intro renders correctly 1] View style{ Object { alignItems: center, backgroundColor: #F5FCFF, flex: 1, justifyContent: center, } } Text style{ Object { fontSize: 20, margin: 10, textAlign: center, } } Welcome to React Native! /Text Text style{ Object { color: #333333, marginBottom: 5, textAlign: center, } } This is a React Native snapshot test. /Text /View ;后续每次运行测试Jest 都会把新的渲染输出与已保存的快照逐一比对。快照文件必须随代码一起提交到版本库。当快照测试失败时需要人工判断这是预期内的变更还是意外回归如果是预期变更使用jest -u--updateSnapshot即可覆盖更新旧快照。2.3 仓库示例中的扩展用法官方示例 examples/react-native/tests/intro.test.js 除了测试Intro组件外还对 React Native 内置组件做了集成测试这些断言同时充当jest-react-native预设的集成测试import {render, screen} from testing-library/react-native; import React from react; import {ActivityIndicator, FlatList, Text, TextInput} from react-native; import Intro from ../Intro; jest.setTimeout(15_000); it(renders correctly, async () { await render(Intro /); expect(screen.toJSON()).toMatchSnapshot(); }); it(renders the ActivityIndicator component, async () { await render(ActivityIndicator animating{true} sizesmall /); expect(screen.toJSON()).toMatchSnapshot(); }); it(renders the TextInput component, async () { await render(TextInput autoCorrect{false} valueapple banana kiwi /); expect(screen.toJSON()).toMatchSnapshot(); }); it(renders the FlatList component, async () { await render( FlatList data{[apple, banana, kiwi]} keyExtractor{item item} renderItem{({item}) Text{item}/Text} /, ); expect(screen.toJSON()).toMatchSnapshot(); });从生成的 intro.test.js.snap 可以看到快照测试的威力——一个简单的FlatList会被完整序列化为底层RCTScrollView及列表项的结构任何渲染细节的变动都会立即暴露在 diff 中exports[renders the FlatList component 1] RCTScrollView data{ [ apple, banana, kiwi, ] } getItem{[Function]} getItemCount{[Function]} keyExtractor{[Function]} onContentSizeChange{[Function]} ... View View ... Text apple /Text /View ... /View /RCTScrollView ;快照的序列化、写入与比对工作由 packages/jest-snapshot 包实现toMatchSnapshot()等匹配器则在 packages/expect 中对外暴露React Native 场景下通过测试库的toJSON()拿到可序列化的组件树后整个流程与 Web 端快照测试完全一致。三、Preset configuration预设配置逐项精讲react-native预设是一套很有主见opinionated的环境配置基于 Meta 在 Facebook 内部的使用经验沉淀而成。它把 React Native 项目的通用配置集中收口但所有配置项都可以像不使用预设时一样被覆盖——这就是预设的灵活性所在。3.1 Environment轻量 Node 环境react-native预设本身就是一个node 环境用于模拟 React Native 应用的运行环境。由于它不加载任何 DOM 或浏览器 API能显著提升 Jest 的启动速度这也是为什么 React Native 测试跑得比 jsdom 场景快。如果你的项目基于拆分后的react-native/jest-preset则 examples/react-native/jest.config.js 中还会看到testEnvironmentOptions.globalsCleanup等环境级选项。3.2 transformIgnorePatterns精准放行需转译的依赖transformIgnorePatterns用于指定哪些文件会被 Babel 转译。问题在于很多react-native的 npm 模块发布时并未预编译源码。默认情况下jest-react-native预设只处理项目自身源码和react-native。如果你引入了需要转译的 npm 依赖可以把这些模块用|分组后加入白名单负向前瞻断言(?!...)表示不被该模式排除{ transformIgnorePatterns: [node_modules/(?!(()?react-native|my-project)/)] }这里(?!...)是正则的负向前瞻negative lookahead断言整条模式的含义是除react-native含 scoped 形式react-native和my-project之外node_modules下所有路径都命中该排除模式、不做转译。你可以在线用正则工具验证哪些路径会被匹配从而被排除转译。一个必须警惕的坑transformIgnorePatterns采用任一模式命中即排除的合并语义。如果把它拆成多条 pattern很容易出现断言互相抵消的隐蔽 bug。下面这个例子中对foo和bar的两条排除断言会彼此抵消结果两个模块都没有被正确排除不是你想要的效果{ transformIgnorePatterns: [node_modules/(?!foo/), node_modules/(?!bar/)] // not what you want }关于该配置项的完整语义默认值、合并规则、典型用例可参考 docs/Configuration.md 中transformIgnorePatterns一节的详细说明其中也明确指出React Native / TypeScript 项目中第三方模块以未转译源码发布导致语法错误时transformIgnorePatterns就是标准的解决方案。3.3 setupFiles每个测试文件运行前的准备工作如果你希望对每一个测试文件注入额外的初始化逻辑可使用setupFiles配置项指定若干脚本这些脚本会在测试框架安装之前执行。它非常适合集中注册本文第四节提到的各类手动 Mock。3.4 moduleNameMapper模块路径重映射moduleNameMapper可以把某个模块路径映射为另一个模块。React Native 预设默认会把所有图片资源映射到一个图片 stub 模块当某个模块如图片、静态资源或特定别名路径在 Jest 环境中无法被解析时就可以用它显式指定替代路径{ moduleNameMapper: { my-module.js: rootDir/path/to/my-module.js } }其中rootDir会被 Jest 替换为配置文件的根目录。该配置项在 packages/jest-config/src/normalize.ts 的归一化流程中会被规范化处理支持字符串或字符串数组两种取值形态。四、Tips用 jest.mock 屏蔽原生模块react-native自带的 Jest 预设内置了若干默认 mock作用于 React Native 仓库本身。但有些 React Native 组件或第三方组件依赖真实原生代码才能渲染在 Jest 的 Node 环境中并不存在对应实现。此时Jest 的手动 Mockmanual mock机制就是标准解法。4.1 简单替换桩组件例如代码依赖第三方原生视频组件react-native-video可以用一个字符串工厂把它替换为桩实现jest.mock(react-native-video, () Video);这样在快照输出中该组件会被渲染为Video {...props} /并且携带全部 props方便你核对传入的属性。关于 Enzyme React 16 场景下快照测试配合 mock 的注意事项可参考文档 docs/TutorialReact.md 中的相关小节。4.2 保留静态字段mockComponent 辅助函数如果你希望 mock 保留原生组件的 propTypes 和静态字段可以直接复用 react-native 提供的mockComponent辅助函数返回一个新的 React 组件jest.mock(path/to/MyNativeComponent, () { const mockComponent require(react-native/jest/mockComponent); return mockComponent(path/to/MyNativeComponent); });4.3 完全自定义的手动 Mock也可以完全手写一个 mock 组件。下面的例子对Text进行自定义 mock并显式转发真实组件的propTypesjest.mock(Text, () { const RealComponent jest.requireActual(Text); const React require(react); class Text extends React.Component { render() { return React.createElement(Text, this.props, this.props.children); } } Text.propTypes RealComponent.propTypes; return Text; });这里jest.requireActual用于在 mock 内部拿到模块的真实实现再借真实实现完善 mock 的静态契约。4.4 非组件类原生模块观察真实模块再建模如果目标是一个非 React 组件的原生模块如NativeModules中的桥接对象上述技法同样适用。官方建议的做法是先阅读该原生模块的源码并在真机上运行 React Native 应用时把模块打印出来观察其真实形态再照着真实模块的形状建模一个手动 Mock从而保证测试中调用的方法、字段与真机一致。4.5 重复 mock 的收敛抽到 setupFiles如果你发现自己反反复复 mock 同一批模块推荐把这些 mock 收敛到一个独立文件中再把它加入setupFiles列表——这样每个测试文件运行前都会自动加载这些 mock避免在每个测试文件里重复书写jest.mock。五、小结围绕 Jest React Native本文覆盖了三条主线初始化preset: react-native或拆分后的react-native/jest-preset配合官方示例 examples/react-native 的完整配置快照测试testing-library/react-nativetoMatchSnapshot()生成并维护.snap文件用jest -u更新预期变更预设调优与 mock 隔离transformIgnorePatterns的负向前瞻写法与合并语义陷阱、setupFiles、moduleNameMapper以及用jest.mock系列技巧屏蔽原生依赖。这套组合拳足以支撑一个真实 React Native 工程在纯 Node 环境下稳定、快速、可回归地运行测试。【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →