十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Ignite React Native 项目测试指南:Jest 单元测试、Mock 与 Maestro 集成测试实战

Ignite React Native 项目测试指南:Jest 单元测试、Mock 与 Maestro 集成测试实战 Ignite React Native 项目测试指南Jest 单元测试、Mock 与 Maestro 集成测试实战【免费下载链接】igniteInfinite Reds battle-tested React Native project boilerplate, along with a CLI, component/model generators, and more! 9 years of continuous development and counting.项目地址: https://gitcode.com/GitHub_Trending/ig/ignite导读本文以 Infinite Red 的 Ignite React Native 项目样板boilerplate为背景系统讲解其官方测试方法论——Write tests. Not too many. Mostly integration.写测试不要太多主要是集成测试。你将掌握Ignite 中 Jest 单元测试的编写结构、Given/When/Then 最佳实践、pnpm run test与pnpm run test:watch的用法、何时该为哪些代码写单测以及jest.fn、jest.mock等 Mock 策略同时了解基于 Maestro 的端到端E2E集成测试与 Ignite 自带测试实例的源码级剖析。测试哲学为什么是Not too many. Mostly integration.Infinite Red 在开发 Ignite 时秉持一个朴素而务实的理念对交付的代码要有信心确信它不会破坏用户的体验。这个理念被浓缩成一句来自 Guillermo Rauch 的名言Write tests. Not too many. Mostly integration. 写测试。不要太多。主要是集成测试。这句话不是一条死板的规则但它非常准确地表达了 Ignite 的测试策略倾向写测试测试是工程质量的一部分不是可选项不要太多并非每行代码都值得写单测过度追求覆盖率反而会拖累开发效率主要是集成测试相比孤立的单元测试Ignite 更重视端到端的集成验证例如用 Maestro 模拟真实用户操作流程因为这类测试最能反映用户实际体验是否被破坏。在这一哲学之下Ignite 的测试体系主要分为两条主线Maestro 集成测试E2E与Jest 单元测试。仓库中 boilerplate/package.json 的 scripts 也印证了这一分工testJest 单测、test:watchJest 监听模式、test:maestroMaestro E2E。Maestro 集成测试以真实用户视角验证完整流程Maestro 是 Ignite 首选的集成测试工具它通过 YAML 描述用户操作流程在真实 App 上执行点击、输入、断言等动作验证从启动到关键页面的完整链路是否可用。Ignite 样板在 boilerplate/.maestro 目录中预置了开箱即用的 Maestro 流程boilerplate/.maestro/flows/Login.yaml主流程使用默认凭据登录并进入演示页面boilerplate/.maestro/flows/FavoritePodcast.yaml播客收藏流程boilerplate/.maestro/shared/_Login.yaml 与_OnFlowStart.yaml可复用的共享子流程。以登录流程为例其 YAML 结构清晰展示了 Maestro 的断言 动作模式#flow: Shared _Login appId: ${MAESTRO_APP_ID} --- - assertVisible: Log In - tapOn: text: Tap to Log in! - assertVisible: Your app, almost ready for launch! - tapOn: text: Lets go! - assertVisible: Components to jump start your project!几个关键点appId使用${MAESTRO_APP_ID}环境变量注入运行测试时通过-e参数传入。Ignite 的 npm 脚本已封装好pnpm run test:maestro对应 boilerplate/package.json 中的maestro test -e MAESTRO_APP_IDcom.helloworld .maestro/flows。runFlow支持流程复用onFlowStart可声明每个流程启动前都要执行的前置步骤这正是共享子流程的设计初衷。断言文本直接对应界面上的真实文案因此 Maestro 测试天然与 i18n 文案耦合——一旦改动了关键界面文案对应的 flow 也需要同步更新。关于 Maestro 的完整安装与入门步骤官方提供了 Ignite Cookbook 配方文档此处不再展开你可以在本地基于已生成的 Ignite 项目ignite new之后直接运行上述预置 flows 体验效果。单元测试Jest 与测试结构单元测试覆盖代码中最小粒度的部分比如单个函数或类。—— React Native 官方文档《Testing》对单元测试的定义在 Ignite 中单元测试主要服务于纯工具函数。测试运行器是Jest测试文件用it或test语句声明——第一个参数是对测试行为的描述第二个参数是执行测试代码的函数it(given a date in the past, colorForDueDate() returns red, () { const input colorForDueDate(2000-10-20) expect(input).toBe(red) })在测试函数内部通过expect函数做断言把被测值作为第一个参数传入expect再调用匹配器方法matcher如.toBe描述期望值。it、test、expect等 Jest 全局函数由 Jest 测试运行器自动加载无需显式 import。测试命名与结构的最佳实践Given / When / ThenAAA编写测试时一个好的测试应当包含以下三段信息Given—— 某个前置条件preconditionWhen—— 被测函数执行的某个动作Then—— 期望的最终结果。这就是业界常说的AAAArrange, Act, Assert模式。Ignite 官方推荐的测试描述风格正是把这三要素直接写进测试名称中例如上例的given a date in the past, colorForDueDate() returns red——given前置条件 When的动作传入日期Then的结果返回红色。如何编写并运行单元测试编写在app或test目录下创建以.test.ts结尾的文件即可例如app/utils/formatDate.test.ts。运行全部单测pnpm run test这会用 Jest 执行全部单元测试。迭代开发模式监听pnpm run test:watchtest:watch启动一个常驻的 Jest 进程每次保存文件都会自动重跑相关测试。在调试逻辑、微调取值时它能提供近乎即时的反馈是开发体验提升的关键工具。何时该写单元测试写测试前最该问的问题是什么代码值得单元测试并非每行代码都能从单测中获益。通常无需外部依赖如 API、且包含非平凡逻辑的代码最适合单测。Ignite 官方给出了三类典型场景复杂的正则表达式正则往往只有在编写的那一刻才读得懂。用一组合法/非法输入去测试能确保未来接手的人包括几个月后的自己不会改坏它。嵌套的 if/else 语句当一个函数重度依赖大量条件分支时靠人肉遍历所有分支几乎不可能。测试能帮助你确保每条分支都被覆盖到。校验类函数比如isJson()这类验证值是否符合特定形状的函数。当关键业务代码的正确性依赖它时必须为它写测试兜底。Mocking让被隔离的代码有依赖可依现实中的代码很少是纯粹的函数。多数 App 存在副作用——发起网络请求、调用原生模块、访问全局对象等。在集成测试中可以通过搭建合适的测试环境来处理这些副作用在单元测试中我们追求隔离被测代码更常见的做法是为外部依赖提供Mock模拟。Jest 提供了多层次的 Mock 策略Ignite 文档逐一给出了用法与示例。Mock Functions模拟回调函数可以用jest.fn创建一个模拟回调函数// 接收输入值加上 42 const mockCallback jest.fn((x) 42 x)它调用起来与普通函数无异const added [0, 1].map(mockCallback)但它的身上额外挂载了.mock等属性供测试中后续断言// 该 mock 函数被调用了两次数组里每项一次 expect(mockCallback.mock.calls.length).toBe(2).mock属性记录了调用次数、调用参数、返回值与this上下文等全部元数据是断言函数如何被调用的核心依据。Mock Modules模拟模块测试依赖axios这类网络库的代码很棘手——返回值受真实网络影响不可控。解决方案是把整个库 Mock 掉返回静态数据import { View, Text } from react-native import axios from axios export const getUsers () axios.get(/users).then((res) res.data)对应的测试import axios from axios import { getUsers } from ./users jest.mock(axios) test(should fetch users, () { const users [{ name: Bob }] const res { data: users } axios.get.mockImplementation(() Promise.resolve(res)) getUsers().then((data) { expect(data).toEqual(users) }) })这里jest.mock(axios)自动把axios替换为 mock 版本mockImplementation则指定axios.get的返回实现从而让每次测试都能拿到确定的数据。Mock React Native 原生模块除了普通 JS 库React Native 的原生模块同样可以 Mock。语法如下jest.mock(react-native-video, () Video);jest.mock的第一个参数是要 Mock 的模块名第二个参数可选是一个返回模块的工厂函数。上面的例子中工厂函数直接返回字符串Video等价于让该模块导出一个默认导出。Ignite 测试基础设施源码剖析理解文档理论后再回看 Ignite 仓库中的实际测试代码能更直观地掌握在真实项目里该怎么做。这些文件同时也可以作为你动手写测试的参照模板。1. Jest 配置极简而关键Ignite 的 Jest 配置非常精简boilerplate/jest.config.jsmodule.exports { preset: jest-expo, setupFiles: [rootDir/test/setup.ts], }preset: jest-expo使用 Expo 官方 Jest 预设自动处理 React Native / Expo 的 Babel 转译与模块解析setupFiles在测试运行前加载 boilerplate/test/setup.ts完成全局 Mock 的初始化。配合 boilerplate/package.json 的 devDependencies可以看到完整测试技术栈jest、jest-expo、testing-library/react-native、react-test-renderer、ts-jest、types/jest、babel-jest。这印证了Jest 单测 Testing Library 组件测试的双层体系。2. 全局 Mocktest/setup.tsboilerplate/test/setup.ts 是 Ignite 测试基建的心脏它在每个测试文件运行前统一 Mock 掉那些在 Node 环境里不可用或不可控的依赖react-native的ImageMock 掉resolveAssetSource返回 mockFile与getSize固定回调success(100, 100)避免测试中加载真实图片资源i18nextMock 成t/translate直接返回key JSON.stringify(params)的简单实现expo-localization固定返回en-US区域设置app/i18nMock 掉 i18n 初始化对象避免测试触发真实的国际化加载流程。这一文件是Mock React Native 模块策略在生产级项目中的完整范例——新建测试文件时你几乎不需要再为这些通用依赖重复写 Mock。3. 纯函数单测范例apiProblem.test.tsboilerplate/app/services/api/apiProblem.test.ts 是对纯函数getGeneralApiProblem的 9 个测试用例完美呼应文档中纯函数最值得单测的观点。它把 apisauce 的错误响应对象映射为业务层友好的结果test(handles connection errors, () { expect(getGeneralApiProblem({ problem: CONNECTION_ERROR } as ApiErrorResponsenull)).toEqual({ kind: cannot-connect, temporary: true, }) }) test(handles unauthorized errors, () { expect( getGeneralApiProblem({ problem: CLIENT_ERROR, status: 401 } as ApiErrorResponsenull), ).toEqual({ kind: unauthorized, }) })注意这里的断言风格连接错误/网络错误/超时/未知错误映射为带temporary: true的对象401/403/404 分别映射为unauthorized/forbidden/not-found而CANCEL_ERROR返回null。这正是校验函数类代码单测的典型写法——输入可控、输出确定、边界齐全。你可以在app目录下创建同名.test.ts文件仿照此模式为其他纯工具函数补测试。4. 依赖副作用单测范例storage.test.tsboilerplate/app/utils/storage/storage.test.ts 展示了如何测试依赖原生模块MMKV的工具函数。它使用describe分组 beforeEach重置环境describe(MMKV Storage, () { beforeEach(() { storage.clearAll() storage.set(string, string) storage.set(object, JSON.stringify(VALUE_OBJECT)) }) it(should save objects, () { save(object, { y: 2 }) expect(loadobject(object)).toEqual({ y: 2 }) }) it(should clear all data, () { expect(storage.getAllKeys()).toEqual([string, object]) clear() expect(storage.getAllKeys()).toEqual([]) }) })其要点在于利用beforeEach让每个用例从相同初始状态出发保证测试相互独立、可重复执行——这是Given 前置条件最佳实践在真实代码中的应用。5. 组件测试范例Text.test.tsx对于 React 组件Ignite 使用React Native Testing Librarytesting-library/react-native它是testing-library/react在 RN 上的移植渲染组件并断言输出import { render } from testing-library/react-native it(should render the component, () { const { getByText } render( ThemeProvider NavigationContainer Text text{testText} / /NavigationContainer /ThemeProvider, ) expect(getByText(testText)).toBeDefined() })完整代码见 boilerplate/app/components/Text.test.tsx。这个示例说明组件测试的重点是渲染后用户能看到什么getByText而不是内部实现细节。值得注意的是被测组件依赖 Theme 与 Navigation 上下文因此测试时要用ThemeProvider、NavigationContainer包裹——这正是集成测试思维在组件层面的体现。6. 全局健壮性测试范例i18n.test.tsboilerplate/test/i18n.test.ts 是 Ignite 内置的一个非常实用的防呆测试它通过grep扫描整个app目录找出所有tx...属性与translate(...)调用中引用的 i18n key再与 boilerplate/app/i18n/en.ts 中定义的翻译 key 比对确保没有漏定义或拼写错误的翻译键避免运行时渲染出空字符串。它的局限也很明确源码注释中已说明如果 key 被存在变量里再条件性地使用就不会被 grep 捕捉到这类 key 需要手动加入文件顶部的EXCEPTIONS数组豁免。这与 docs/boilerplate/test/Test.md 中对test目录的说明一致——Jest 测试可以放在代码库任何位置但全局性的 Jest 初始化、Mock 与全局作用域测试统一归入test目录。测试资源与选型参考如果要在 Ignite App 中扩展测试能力以下是官方推荐的生态方向React Native Testing Librarytesting-library/react的 RN 移植版最适合做组件单元测试Ignite 已将其预置在 devDependencies 中Detox 与 AppiumMaestro 之外的两款集成测试替代方案可按团队习惯与 CI 环境选择Jest 官方 React Native 教程涵盖 Mock 原生模块、快照测试等进阶用法Maestro 官方Why Maestro文档阐述其设计动机与使用场景Kent C. Dodds 的测试文章关于写多少测试、测什么的深入思考与 Ignite 的测试哲学一脉相承。小结回到开篇那句Write tests. Not too many. Mostly integration.Ignite 的测试体系可以概括为一条清晰的实践路径以 Maestro 覆盖核心用户流程登录、收藏等守住体验底线以 Jest pnpm run test守护纯函数与工具逻辑用test:watch在开发中保持快速反馈用jest.fn/jest.mock隔离副作用借助 boilerplate/test/setup.ts 的全局 Mock 免去重复劳动参照仓库内四个测试实例apiProblem / storage / Text / i18n建立自己的测试模板覆盖纯函数、依赖副作用、组件渲染与全局资源四类场景。无论你是刚用ignite new创建项目还是在维护老应用这套方法论都能直接迁移到你的日常开发中——先写给用户看的测试Maestro再写给逻辑兜底的测试Jest而不要陷入为覆盖率而测试的陷阱。【免费下载链接】igniteInfinite Reds battle-tested React Native project boilerplate, along with a CLI, component/model generators, and more! 9 years of continuous development and counting.项目地址: https://gitcode.com/GitHub_Trending/ig/ignite创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表