Jest是一个由Facebook维护的测试框架,在这篇文章中我们将学习一下如何使用Jest来测试我们的ReactJS组件。在学习它提供的使测试React apps更容易的新特性之前,我们先了解一下如何用Jest测试简单的JavaScript函数,Jest没有特别针对React,你可以用它测试任何JavaScript程序。然而,它提供了一些特性便于测试用户接口,这也是为什么它非常适合React。 |
示例应用程序
|
1
2
3
4
|
render( <Todos />, document.getElementById( 'app' ) ); |
Todos 组件是这个程序的重中之重,它包含了所有的状态(在这个程序里面是硬编码的——实际应用中要从一个API或者是其它类似的来源那里获取才好),还有一些代码是用来渲染两个子组件的: Todo, 在状态里面的每一个待办事项都会让它被渲染一遍,还有就是 AddTodo, 它会被渲染一次,提供给用户一个表单来添加新的待办事项。
因为 Todos 组件包含了所有的状态, 所以就需要 Todo 和 AddTodo 组件来通知它啥时候有改变发生。由此它会在某些数据发生改变时给这些组件下发一些函数来调用,而 Todos 也就能够有依据地对状态进行更新。
最后,也就是现在,你会发现,所有的业务逻辑都包含在了app/state-functions.js中。
这些都是纯函数,它们可以接受状态和一些数据,并返回新的状态。如果你对纯函数不太熟悉的话,他们只是提供一些参考数据的函数,并没有其它什么副作用。要了解更多相关内容,你可以阅读我在A List Apart上面写的有关纯函数的文章以及SitePoint上有关纯函数和React的文章 。 |
用还是不用 TDD 方法呢?
|
安装和配置Jest
|
1
|
npm install --save-dev babel-jest babel-polyfill babel-preset-es2015 babel-preset-react jest |
你还需要为Babel配置一个.babelrc文件,这样可以使用所需的任何预设及插件。示例项目已经包含了该文件,就像:
1
2
3
|
{ "presets" : [ "es2015" , "react" ] } |
我们还没有安装任何的React测试工具,因为我们还不打算开始测试我们的部件,但是我们的状态函数除外。
Jest希望在__tests__文件夹中找到我们的测试,这已成为JavaScript社区的惯例,我们也打算坚持这样做。如果你不喜欢__tests__这个设置,Jest也支持寻找任意的.test.js和.spec.js文件。
因为我们要测试我们的状态函数,所以继续下一步,创建__tests__/state-functions.test.js。
然后我们将编写一个测试文件,不过现在要进行dummy测试,这样可以检查所有工作是否正常以及对Jest的配置情况。
1
2
3
4
5
|
describe( 'Addition' , () => { it( 'knows that 2 and 2 make 4' , () => { expect(2 + 2).toBe(4); }); }); |
现在,进入你的package.json。我们需要设置npm test,以便它运行Jest,只需通过设置test脚本来运行Jest就可以。
1
2
3
|
"scripts" : { "test" : "jest" } |
如果你现在在本地运行npm test,你可以看到测试运行正常,并通过。
1
2
3
4
5
6
7
8
|
PASS __tests__/state-functions.test.js Addition ✓ knows that 2 and 2 make 4 (5ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 passed, 0 total Time: 3.11s |
如果你曾经使用过Jasmine,或者大多数测试框架,那么对于以上的测试代码你应该相当熟悉。Jest让我们根据需要进行usedescribeanditto nest测试。至于要使用多少个nest完全取决于你了;我喜欢所有的描述性字符可以通过,todescribeanditread几乎是一个完整的句子。
当要进行实际的声明时,你会希望在此之前通过expect()调用进行测试。在这种情况下,我们使用toBe。你可以在Jest文档找到所有可用的声明列表,toBe会使用===检查给定的值是否匹配测试中的值。在本教程中,我们会遇到几个Jest的声明。
测试业务逻辑
|
1
2
3
4
5
6
7
8
|
import { toggleDone } from '../app/state-functions' ; describe( 'toggleDone' , () => { describe( 'when given an incomplete todo' , () => { it( 'marks the todo as completed' , () => { }); }); }); |
而用test则可能是下面这个样子:
1
2
3
4
|
import { toggleDone } from '../app/state-functions' ; test( 'toggleDone completes an incomplete todo' , () => { }); |
test方法的看起来更好,但是缩进可能会少一点。这主要与个人喜好相关,你可以选一个你更习惯的书写方式。
接着就可以写断言了。首先,在传入toggleDone之前先创建一个初始状态,同时传入需要转换状态的todo的ID。基于toggleDone方法返回的完成状态,我们这样写断言:
1
2
3
4
5
6
7
8
9
|
const startState = { todos: [{ id: 1, done: false , name: 'Buy Milk' }] }; const finState = toggleDone(startState, 1); expect(finState.todos).toEqual([ { id: 1, done: true , name: 'Buy Milk' } ]); |
注意这里是用toEqual作的断言。对于原始类型,如字符串和数字,应该使用toBe方法。toEqual是用于处理数字和对象的,它会递归的比较给定对象的所有字段或者元素以确认相等。
至此,我们便可以运行npm test,然后可以看到这个状态方法通过了测试:
1
2
3
4
5
6
7
|
PASS __tests__/state-functions.test.js ✓ tooggleDone completes an incomplete todo (9ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 passed, 0 total Time: 3.166s |
变更后重新运行测试
|
测试React组件出于其意义甚微,事实上我一般不推荐为React组件写太多的测试。你需要测试的东西,比如业务逻辑,本来就应该从组件中抽离出去,然后落实到独立的方法中去,比如我们上文测试的状态方法。也即是说,测试React的交互(比如,确保当用户点击一个按钮是指定的方法会以正确的参数被调用)只是偶尔才有用。我们将从测试React组件可以渲染正确的数据开始测试,然后测试交互。然后进行的是快照,Jest的一个可以使测试React组件输出变得方便的特性。 因此我们必须使用react-addons-test-utils,一个提供了测试React方法的库。还需要安装Enzyme,AirBnB写的一个可以使测试React组件变得简单的包装库。我们将在所有的测试中使用这个API。Enzyme是一个十分杰出的库,甚至React团队都在推荐用它测试React组件。
我们将测试Todo组件能否将它的todo文本渲染到一个段落。首先要创建__tests__/todo.test.js,然后导入我们的组件:
这里也从Enzyme中导入了mount。mount方法是用于渲染组件,其输出则用于检查和断言。虽然是在Node中测试,我们却可以在写测试时引入DOM元素。因为Jest配置了jsdom,一个在Node中实现了DOM的库。这是一个很棒的库,通过它我们就可以写基于DOM的测试,且在测试的时候不用每次都打开浏览器。 |
可以用mount方法创建Todo:
然后调用wrapper.find方法,传入一个CSS选择器,用于查找需要包含Todo内容的段落。这个API可能会让你想起jQuery,因为它是故意这么设计的。在渲染的输出中查找匹配的元素用时使用这个API看起来十分直观:
最后,断言它包含的文本是Buy Milk:
完整的测试代码将会是下面这个样子:
哇哦!可能你觉得为了测试‘Buy Milk’可以显示到正确的位置的确需要很多的工作,呃……确实如此。但是先不必着急,下一节将会看到用Jest的快照功能会使这个测试变简单很多。 此时请先看一下,如何用Jest的监听功能来断言方法是否以指定参数调用。对于本例来说这个测试是很有用的,因为我们的Todo组件包含了两个函数类型的属性,它们是在用户点击按钮或者触发交互时被调用的。 下面的测试会断言当点击todo按钮的时候,会调用给定组件的doneChange方法。
现在需要有个可以跟踪方法调用以及调用时的参数的函数。然后就可以检查当用户点击todo时,doneChange会被以正确的参数调用。幸好,Jest提供了开箱即用的监听功能。监听方法是无需关注其实现,只需考虑其调用时机及方式的方法。可以想象为你在监视那个方法。可以调用Jest.fn()创建:
返回的方法可以用于监听以检查其是否被正确调用。一开始要渲染一个有正确属性的Todo:
接着,再次查找那个段落,如同上一个测试:
然后可以调用它的simulate方法,模拟用户事件,传入参数click:
最后只需断言监视方法可以被正确调用。本例中,期望调用时传入todo的ID也就是1。可以用expect(doneChange).toBeCalledWith(1)来断言,此时也完成了我们的测试!
|
使用组件的快速测试更好我上面提及的看起来测试React组件需要大量工作,特别是一些比较通用功能(比如渲染文本)。Jest允许你快速测试,而不是在React组件里使用大量断言。这些对于交互没有多大用处(但是我依然选择使用我上面提及的),但是对于测试组件的输出是否正确,它使用起来非常简单。 当你运行一个快速测试,Jest在测试时渲染React组件,并且益JSON格式文件来存储结果。每次运行测试,Jest将会快速检测React组件是否依然渲染相同输出。然后,当你改变一个组件的行为,Jest将会告诉你下面的其中一种情况:
这种测试方式意味着:
你也不用对你所有组件进行快速测试,事实上,我不推荐这种方式。你可以选择一些需要确认的组件进行功能测试。快速测试你所有组件将会使得你的测试周期变长,效率更低。请谨记,React是一个非常好用的测试框架,所以我们大可以对它的测试结果放心。请确认你没有结束测试框架,而不是你的代码。
|
进行快照测试之前,还需要另一个Node包。react-test-renderer是一个将React组件渲染成一个纯JavaScript对象的包。也意味着其结果可以被存储成文件,这也正是Jest用来跟踪快照的方法。
下面开始用快照方式重写第一个Todo组件测试。先将TodoComponent中点击todo调用doneChange的测试注释掉。 首先要做的是引入react-test-render,并删除mount的引用。它们不能同时使用,只能使用其中之一。所以我们要先注释掉那个测试。
然后用刚才导入的renderer渲染组件,并断言它与快照相符:
第一次运行这段代码,Jest可以发现没有这个组件的快照,然后会创建一个。看一下__tests__/__snapshots__/todo.test.js.snap:
可以看到,Jest将输出保存了起来,下一次运行这个测试的时候,它会检查输出是否与快照相同。为了举例,先将组件破坏,将渲染了todo文本的段落移除,也就是将这段代码从TodoComponent中删除:
现在看看Jest的输出:
Jest发现快照与新组件并不匹配,在结果中就体现出来了。如果这次变更是正常的,可以在运行jest时加上-u参数,这将会更新快照。但是本例中,先重置变更,Jest会再次变得高兴。 下一步,想想该如何使用快照测试交互。同一个测试可以拥有多个快照,所以可以测试交互后的输出与预期是否相符。 其实,并不能用Jest快照测试我们Todo组件的交互,因为他们不控制本身的状态,而是调用其callback属性上的回调函数。本例中已经将快照测试移到一个新文件todo.snapshot.test.js,将原先的toggling测试保留在 todo.test.js。将快照测试单独放到一个文件是有用的,也意味着消除了react-test-renderer 和 react-addons-test-utils之间的冲突。 注意,在GitHub 上可以找到这个教程中全部的代码,也可以切到你本地运行。 |
结论Facebook发布Jest已经有很长一段时间,但在最近几个月它才被挖出来并充分被使用。它很快得到JavaScript开发人员的青睐,并将会变得更好。如果过去试用过Jest,但对它并不满意,我建议你再试一次,因为它现已做出非常大的改变:运行快,操作规范,错误消息提示奇特,更妙的是它还具备快照功能。 |
本文标题:如何使用 Jest 测试 React 组件
本文地址:https://www.oschina.net/translate/test-react-components-jest
参与翻译:debugging, leoxu, 昌伟兄, 独孤俊杰, xufuji456, 清清鸟, 无若
英文原文:How to Test React Components Using Jest