原文链接: www.callstack.com/blog/testin…
原文链接: Ferran Negre
在 React 生态中,一个广为人知的范式是:对于任何大型应用,都应将业务逻辑(即管理应用状态)从组件中抽离。绝大多数组件应作为可复用的代码片段,仅根据应用状态渲染 UI,而状态变化主要由用户输入和 API 调用触发。React Native 同样遵循这一范式。
本文将结合 React Native 探讨 Redux 测试,因其测试机制简便易行。但您可自由将相同概念应用于其他状态管理方案。具体内容如下:
测试 reducer:手动比对 vs 快照 模拟 React Native 内置 fetch 功能 重新模拟 redux-mock-store 测试动作(含异步动作):手动比对 vs 快照 关于 Jest 与 React Native 的集成配置,请参阅使用新版 Jest 进行 React Native 单元测试(上)。
Reducer
测试 reducer 可能是应用中最容易测试的部分之一:给定当前状态和一个动作,reducer 在应用该动作后应返回新状态。让我们看看如何测试这个示例的一部分:
我们将重点测试 LOAD_REPOS.success 动作。创建测试的第 1 版:
又得复制粘贴了 :(
相当简短吧?我直接复制粘贴了reducer的初始状态,并添加了变化部分(repos)。当你在大型项目中工作时,随着状态逐渐膨胀(或进行重构),你会发现这个过程极其重复,需要更优解——于是诞生了版本2:
重新实现reducer :(
这样更好吧?我可以在状态中随意增删内容(除了isLoading和repos部分),测试都不会出错。只有当action.success未来确实影响到状态的其他部分时,才需要回到这里修改。但是,你不觉得这个测试其实和下面这个非常相似吗:
我们测试中做的本质上是重新实现 reducer。每次修改 reducer 逻辑时,我都得回到这里重复同样的工作。太重复了……有没有更好的办法?让我们截取输出结果。第三版登场:
给定状态和动作,为我生成输出
我明白,这只需一行代码且完全避免了重复(DRY原则?)。你喜欢吗?我很喜欢!现在看看快照效果:
哇!我居然能看到执行操作后状态的确切变化。正如你所见,我们让Jest承担了繁重的工作,自己只需旁观。此时若增删状态的任何部分,测试就会失败:这完全符合预期,因为输出的状态必然不同。但正如本系列前文所述,我们只需完成两件事:
- 检查Jest的错误输出
- 若结果符合预期,使用-u参数重新运行测试以再生成快照:
- 若结果不符合预期,显然需要去修复它
这充分展现了测试驱动开发(TDD)的快速便捷性:
- 先编写测试
- 启用Jest监视模式开始编写reducer
- 当输出快照符合预期时,告知Jest保存快照
- 完成!
动作
简单的动作创建器就是一个返回对象的函数:
简单动作创建器
可通过以下方式测试:
第二部分,让我们查看生成的快照:
没错,这与reducer完全相同:我(和我的团队)可以直接读取输出结果,无需重新创建动作(这意味着无需在两处维护相同代码)。
顺便一提,若您已厌倦阅读,可观看Kent C. Dodds制作的精炼短视频讲解相同概念。若您仍感兴趣,请继续阅读。鉴于测试此类简单动作实在乏味,我们转而测试一个异步动作——它将调用React Native内置的fetch功能,并一次性分发多个动作。
异步操作
这是我们将要测试的异步操作:
我们需要一些模拟辅助来实现优雅的测试方案,所以...开始模拟吧!
模拟 React Native 内置的 fetch 功能
类似于在 Web 应用中使用 whatwg-fetch 实现 window.fetch 的 polyfill,React Native 采用相同思路,将 fetch 功能封装为全局对象 global.fetch。
单元测试中我们不希望进行真实的 API 调用,但需要模拟响应结果以编写更完善的断言。虽然已有现成的模拟库,但我编写了更精简的版本以满足需求:
模拟fetch
接着可通过package.json的setupFiles字段告知Jest加载该配置:
后续测试异步操作时将展示该模拟库的应用。现在继续模拟工作。
模拟 Redux 存储
我相信很多 Redux 用户都熟悉 redux-mock-store。这是一个简单的辅助工具,它提供 Redux 存储的模拟版本,使测试哪些动作被调用变得非常容易。一旦你有了模拟存储,就可以使用 store.dispatch 并通过 store.getActions 获取已分发的动作列表。
但你知道吗?我实在懒得在每个需要存储和thunk的测试中重复相同操作,因此决定重新模拟redux-mock-store模块。在Jest中,你可以在node_modules同级创建mocks文件夹来模拟任何npm模块。因此我只需在__mocks__文件夹创建名为redux-mock-store.js的文件(即模块名称):
然后在测试中这样使用:
够懒吧?
异步测试
我觉得我们现在可以测试异步操作了,对吧?基本要测试的内容是:
- 成功请求应触发请求与成功操作
- 失败请求应触发请求与失败操作
第一种情况:
相当简短,对吧?
那么会生成什么输出?
正如预期。这种情况我认为非常实用且易于阅读:快速通过"type"字段确认类型,深入检查时只需核对"data"是否与fetch返回的预期值匹配。
第二种情况非常相似:
其输出如下:
可见当模拟对象就位后,编写此类测试变得极其直观。而且通常无需额外模拟——现成的模拟对象随处可得,或可自行编写并复用至其他项目。
总结
至此,加上第一部分的内容,我们基本涵盖了(希望如此)React Native组件及其状态管理逻辑的单元测试。我们还重新认识了Jest及其新功能:快照。相反,我确信随着Jest的持续发展,React Native应用的测试方式也将不断改进。因此本文可能很快就会过时!
最后分享几点个人见解:
为何选择Jest?
- 它能轻松测试React与React Native组件
- 快照功能虽是亮点,但完全可选
- 借助Jest可轻松测试Redux等状态管理库:网页与移动端共享的逻辑也能共享测试!
- React Native与Jest出自同一开发社区。这意味着什么?未来他们将最致力于确保React Native与Jest的完美协同
总结:若您用Jest测试网页与移动应用,即可践行React Native的核心理念:
一次学习,随处编写——React Native
当然,这仅代表个人观点。希望您享受这次用新版Jest测试React Native应用的短暂旅程,并谨记……测试愉快!
您也可了解我们React Native开发公司提供的服务。