图片来自 https://blog.algolia.com 在上一篇文章中我们介绍了什么是单元测试以及单元测试框架 Jest 的基本用法。在本文中我们会具体聊聊如何用 Jest 为 React 组件编写单元测试。
首先我们要明确的一点,那就是 React 组件的单元测试本质是也是单元测试。因此它也符合我们之前介绍过的单元测试的全部特点。唯一不同的地方在于 React 组件的单元测试中我们需要找到合适的方法对执行结果进行断言。换言之,我们要根据 React 的特点来设置代码是否正确执行的判断条件。
那么 React 组件和其它的被测试对象有何不同呢?仔细想过,我们会发现:
React 组件的 render 结果是一个组件树,并且整个树最终会被解析成一个纯粹由 HTML 元素构成的树形结构 React 组件可以拥有 state,且 state 的变化会影响 render 结果 React 组件可以拥有生命周期函数,这些生命周期函数会在特定时间点执行 知道了要测试的内容,接下来的问题就是如何执行一个 React 组件并编写断言了。如何执行一个 React 组件呢?看到这个问题估计大多数儿人是蒙的。平时不就是直接 ReactDOM.render 吗?不错,ReactDOM.render 确实可以执行一个 React 组件并将它渲染到页面中,但这种方式不利于编写测试代码。
有没有更简单的方式呢?其实 React 已经帮我们提供好了工具,让我们一起来看看。
React 提供的测试工具 在 React 的官方文档中提到了两个用于测试 React 组件的库。让我们分别介绍。
react-test-renderer 在说 react-test-renderer 之前,让我们先聊聊什么是 renderer。React 最早是被用来开发网页的,所以早期的 React 库中还包含了大量和 DOM 相关的逻辑。后来 React 的设计思想慢慢被迁移到其它场景,最被人们熟知的莫过于 React Native 了。为了灵活性和扩展性,React 的代码被分拆为 React 核心代码与各种 renderer。React 自带了 3 个 renderer,前两个是大家常见的:
最近和小伙伴们在尝试做把项目中经常用到的 React 组件整理成一套 React 组件库。为了保证代码质量且方便日后维护,我们决定为组件编写单元测试。接下来几篇文章会聊聊在这个过程中我们遇到的问题以及一些思考。
正文开始 要写单元测试,首先面临的一个问题就是“我该使用哪个测试框架”?
如果你去 Google 上搜索“how to test react component”,在结果页面中你大概会来来回回看到以下几个名词/概念:
Jest Enzyme Mocha全家桶(套餐内容大概率是:Mocha + Chai + Sinon + Istanbul) react-test-renderer react-dom/test-utils 首先让我们来看看这些库的功能和定位,然后再来做选择。
🃏 Jest Jest 是 Facebook 开发的一款 JavaScript 测试框架。在 Facebook 内部广泛用来测试各种 JavaScript 代码。其官网上主要列出了以下几个特点:
轻松上手 使用 create-react-app 或是 react-native init 创建的项目已经默认集成了 Jest 现有项目,只需创建一个名为 __test__ 的目录,然后在该目录中创建以 .spec.js 或 .test.js 结尾的文件即可 内置强大的断言与 mock 功能 内置测试覆盖率统计功能 内置 Snapshot 机制 虽然 Jest 官网介绍中多次 React,但实际上 Jest 并不是和 React 绑定的。你可以使用它测试任何 JavaScript 项目。
Context API 可以说是 React 中最有趣的一个特性了。一方面很多流行的框架(例如react-redux、mobx-react、react-router等)都在使用它;另一方面官方文档中却不推荐我们使用它。在 Context API 的文档中有下面这段话:
The vast majority of applications do not need to use context.
If you want your application to be stable, don’t use context. It is an experimental API and it is likely to break in future releases of React.
为何会出现这种情况呢?这还得让我们从现有版本 Context API 要解决的问题已经它自身的缺陷说起。
现有版本 Context API 的使用场景以及缺陷 我们都知道在 React 中父子组件可以通过 props 自顶向下的传递数据。但是当组件深度嵌套时,从顶层组件向最内层组件传递数据就不那么方便了。手动在每一层组件上逐级传递 prop 不仅书写起来很繁琐同时还会为夹在中间的组件引入不必要的 prop。这时 Context API 就派上用场了。你只需要在外层组件上声明要传递给子组件的 Context:
Redux 是一个很棒的用于管理应用程序“状态”的工具。单向数据流以及对不可变数据的关注使得推断状态的变化变得很简单。每次状态变化都由一个 action 触发,这会导致 reducer 函数返回一个变更后的新状态。由于客户要在我们的平台上管理或发布广告资源,在 AppNexus 使用 Redux 创建的很多用户界面都需要处理大量数据以及非常复杂的交互。在开发这些界面的过程中,我们发现了一些有用的规则和技巧以维持 Redux 易于管理。以下的几点讨论应该可以帮助到任何在大型,数据密集型应用中使用 Redux 的开发者:
第一点: 在存储和访问状态时使用索引和选择器 第二点: 把数据对象,对数据对象的修改以及其它 UI 状态区分开 第三点: 在单页应用的不同页面间共享数据,以及何时不该这么做 第四点: 在状态中的不同节点复用通用的 reducer 函数 第五点: 连接 React 组件与 Redux 状态的最佳实践 1. 使用索引保存数据,使用选择器读取数据 选择正确的数据结构可以对程序的结构和性能产生很大影响。存储来自 API 的可序列化数据可以极大的受益于索引的使用。索引是指一个 JavaScript 对象,其键是我们要存储的数据对象的 id,其值则是这些数据对象自身。这种模式和使用 hashmap 存储数据非常类似,在查询效率方面也有相同的优势。这一点对于精通 Redux 的人来说不足为奇。实际上,Redux 的作者 Dan Abramov 在它的 Redux 教程中就推荐了这种数据结构。
设想你有一组从 REST API 获取的数据对象,例如来自 /users 服务的数据。假设我们决定直接将这个普通数组存储在状态中,就像在响应中那样。当我们需要获取一个特定用户对象时会怎样呢?我们需要遍历状态中的所有用户。如果用户很多,这可能会是一个代价高昂的操作。如果我们想跟踪用户的一小部分,例如选中和未选中的用户呢?我们要么需要把数据保存在两个数组中,要么就要跟踪这些选中和未选中用户在主数组中的索引(译者注:此处指的是普通意义上的数组索引)。
然而,我们决定重构代码改用索引的方式存储数据。我们可以在 reducer 中以如下的方式存储数据:
{ "usersById": { 123: { id: 123, name: "Jane Doe", email: "jdoe@example.
React Conf 2017的全部视频已经在YouTube上放出来了。为了方便在家里的电视上播放,就全部下载了一套。顺便上传了百度网盘一份。感兴趣的可以看看哦。全部720p。
下载地址:
http://pan.baidu.com/s/1gfl5FzX