本文转载自:众成翻译
译者:iOSDevLog
连接:http://www.zcfy.cc/article/3806
原文:https://www.fullstackreact.com/30-days-of-react/day-25/html
今天,咱们将看看一个由Airbnb所维护的开源库,名为Enzyme,使得测试变得简单易用。react
昨天咱们使用了react-addons-test-utils
库来编写咱们对Timeline
组件的第一个测试。可是, 此库是至关低级的, 使用起来可能有点麻烦。Enzyme是由 AirBnb 团队发布和维护的测试实用程序库, 它提供了一个更好的、高级的 API 来处理测试中的React组件。npm
咱们在测试咱们的 <Timeline />
组件:api
咱们将使用Enzyme, 使这些测试更容易写和更可读。浏览器
昨天, 咱们写了咱们的第一个测试以下:react-router
import React from 'react'; import TestUtils from 'react-addons-test-utils'; import Timeline from '../Timeline'; describe('Timeline', () => { it('wraps content in a div with .notificationsFrame class', () => { const wrapper = TestUtils.renderIntoDocument(<Timeline />); TestUtils .findRenderedDOMComponentWithClass(wrapper, 'notificationsFrame'); }); })
虽然这是可行的, 但它不是世界上最容易阅读的测试。当用Enzyme咱们重写它时让咱们看看这个测试的样子。app
咱们能够只测试组件的输出, 而不是用Enzyme来测试完整的组件树。将不渲染任何组件的子级。这称为 浅 渲染。dom
Enzyme使浅渲染超容易。咱们将使用Enzyme导出的shallow
函数来装载咱们的组件。函数
让咱们更新src/components/Timeline/__tests__/Timeline-test.js
文件, 包括从 enzyme
导入shallow
函数:测试
import React from 'react'; import { shallow } from 'enzyme'; describe('Timeline', () => { it('wraps content in a div with .notificationsFrame class', () => { // our tests }); })
react-addons-test-utils
也支持浅渲染。事实上, Enzyme素只是包装这个函数。虽然昨天咱们没有使用浅渲染, 但若是咱们使用它看起来像这样:
> const renderer = ReactTestUtils.createRenderer(); > renderer.render(<Timeline />) > const result = renderer.getRenderOutput(); >
如今, 为了渲染咱们的组件, 咱们可使用shallow
方法并将结果存储在一个变量中。而后, 咱们将为在其虚拟 dom 中渲染的不一样的React元素 (HTML 或子组件) 查询 渲染的组件。
整个断言包括两行:
import React from 'react'; import { shallow, mount } from 'enzyme'; import Timeline from '../Timeline'; describe('Timeline', () => { let wrapper; it('wraps content in a div with .notificationsFrame class', () => { wrapper = shallow(<Timeline />); expect(wrapper.find('.notificationsFrame').length).toEqual(1); }); it('has a title of Timeline', () => { wrapper = mount(<Timeline />) expect(wrapper.find('.title').text()).toBe("Timeline") }) describe('search button', () => { let search; beforeEach(() => wrapper = mount(<Timeline />)) beforeEach(() => search = wrapper.find('input.searchInput')) it('starts out hidden', () => { expect(search.hasClass('active')).toBeFalsy() }) it('becomes visible after being clicked on', () => { const icon = wrapper.find('.searchIcon') icon.simulate('click') expect(search.hasClass('active')).toBeTruthy() }) }) describe('status updates', () => { it('has 4 status updates at minimum', () => { wrapper = shallow(<Timeline />) expect( wrapper.find('ActivityItem').length ).toBeGreaterThan(3) }) }) })
咱们可使用yarn test
命令 (或 npm test
命令) 同样的方式运行测试:
yarn test
咱们的测试经过, 而且更易于阅读和维护。
让咱们继续写断言, 从咱们昨天开始的假设列表中抽取。咱们将首先构建咱们的测试套件的其他部分, 写出咱们的describe
和it
块。咱们将填写的规格与断言后:
import React from 'react'; import { shallow } from 'enzyme'; import Timeline from '../Timeline'; describe('Timeline', () => { let wrapper; it('wraps content in a div with .notificationsFrame class', () => { wrapper = shallow(<Timeline />); expect(wrapper.find('.notificationsFrame').length).toEqual(1); }); it('has a title of Timeline') describe('search button', () => { it('starts out hidden') it('becomes visible after being clicked on') }) describe('status updates', () => { it('has 4 status updates at minimum') }) })
若是咱们遵循测试驱动开发 (简称 TDD), 咱们将首先编写这些假设, 而后构建组件以经过这些测试。
让咱们填写这些测试, 以便它们经过咱们现有的Timeline
组件。
咱们的标题测试比较简单。咱们将查找标题元素并确认标题为Timeline
。
咱们但愿标题能够在 .title
类下使用。所以, 要在规范中使用 .title
类, 咱们只需使用Enzyme所暴露的find
函数便可获取组件。
由于咱们的Header
组件是 Timeline
组件的子组件, 因此不能使用shallow()
方法。相反, 咱们必须使用Enzyme提供的 mount()
方法。
Shallow? Mount?
shallow()
渲染函数只渲染咱们专门测试的组件, 它不会渲染子元素。相反, 咱们将不得不mount()
组件, 由于子组件Header
不可用的 jsdom, 不然。咱们将在本文的末尾看到更多的Enzyme函数。
如今让咱们填写标题描述:
import React from 'react'; import { shallow, mount } from 'enzyme'; import Timeline from '../Timeline'; describe('Timeline', () => { let wrapper; it('wraps content in a div with .notificationsFrame class', () => { wrapper = shallow(<Timeline />); expect(wrapper.find('.notificationsFrame').length).toEqual(1); }); it('has a title of Timeline', () => { wrapper = mount(<Timeline />) // notice the `mount` expect(wrapper.find('.title').text()).toBe("Timeline") }) })
运行咱们的测试, 咱们将看到这两个指望经过:
接下来, 让咱们更新咱们的搜索按钮测试。咱们在这里有两个测试, 其中一个要求咱们测试一个交互。Enzyme为处理相互做用提供了一个很是干净的界面。让咱们来看看如何根据搜索图标编写测试。
一样, 因为咱们在时间轴中对子元素进行测试, 所以咱们必须mount()
元素。由于咱们要在一个嵌套的describe()
块中编写两个测试, 因此咱们能够在帮助器以前编写一个新的 mount()
来为每一个测试从新建立, 这样它们是纯的。
此外, 咱们还将使用 input.searchInput
元素进行两个测试, 所以, 让咱们在前面的帮助器中为该元素编写.find()
。
describe('Timeline', () => { let wrapper; // ... describe('search button', () => { let search; beforeEach(() => wrapper = mount(<Timeline />)) beforeEach(() => search = wrapper.find('input.searchInput')) // ... }) })
若要测试是否隐藏了搜索输入, 咱们只须要知道是否应用了active
类。Enzyme为咱们提供了一种使用 hasClass()
方法检测组件是否有类的方法。让咱们填写第一个测试, 指望搜索输入没有活动类:
describe('Timeline', () => { let wrapper; // ... describe('search button', () => { let search; beforeEach(() => wrapper = mount(<Timeline />)) beforeEach(() => search = wrapper.find('input.searchInput')) it('starts out hidden', () => { expect(search.hasClass('active')).toBeFalsy() }) it('becomes visible after being clicked on') // ... }) })
关于第二个测试的棘手部分是, 咱们须要点击图标元素。在咱们看如何作到这一点以前, 让咱们先找到它。咱们能够在包装上的目标经过它的 .searchIcon
类定位到它。
it('becomes visible after being clicked on', () => { const icon = wrapper.find('.searchIcon') })
如今, 咱们有了图标, 咱们想模拟一个点击元素。回想一下, onClick()
方法实际上只是浏览器事件的门面。即, 单击一个元素只是一个经过组件冒泡的事件。而不是控制鼠标或调用元素上的click
, 咱们将模拟发生在它上的事件。对咱们来讲, 这将是click
事件。
咱们将在icon
上使用simulate()
方法来建立此事件:
it('becomes visible after being clicked on', () => { const icon = wrapper.find('.searchIcon') icon.simulate('click') })
如今咱们能够设定一个search
组件具备active
类的指望。
it('becomes visible after being clicked on', () => { const icon = wrapper.find('.searchIcon') icon.simulate('click') expect(search.hasClass('active')).toBeTruthy() })
咱们对Timeline
组件的最后指望是至少有四状态更新。当咱们将这些元素放置在Timeline
组件上时, 咱们能够 "浅" 渲染组件。此外, 因为每一个元素都是自定义组件, 所以咱们能够搜索'ActivityItem'类型的特定组件的列表。
describe('status updates', () => { it('has 4 status updates at minimum', () => { wrapper = shallow(<Timeline />) // ... }) })
如今, 咱们能够测试ActivityItem
组件列表的长度。咱们将设定咱们的指望, 若是长度至少是4的名单。
describe('status updates', () => { it('has 4 status updates at minimum', () => { wrapper = shallow(<Timeline />) expect( wrapper.find('ActivityItem').length ).toBeGreaterThan(3) }) })
咱们如今的整个测试套件以下所示:
import React from 'react'; import { shallow, mount } from 'enzyme'; import Timeline from '../Timeline'; describe('Timeline', () => { let wrapper; it('wraps content in a div with .notificationsFrame class', () => { wrapper = shallow(<Timeline />); expect(wrapper.find('.notificationsFrame').length).toEqual(1); }); it('has a title of Timeline', () => { wrapper = mount(<Timeline />) expect(wrapper.find('.title').text()).toBe("Timeline") }) describe('search button', () => { let search; beforeEach(() => wrapper = mount(<Timeline />)) beforeEach(() => search = wrapper.find('input.searchInput')) it('starts out hidden', () => { expect(search.hasClass('active')).toBeFalsy() }) it('becomes visible after being clicked on', () => { const icon = wrapper.find('.searchIcon') icon.simulate('click') expect(search.hasClass('active')).toBeTruthy() }) }) describe('status updates', () => { it('has 4 status updates at minimum', () => { wrapper = shallow(<Timeline />) expect( wrapper.find('ActivityItem').length ).toBeGreaterThan(3) }) }) })
find()
处理什么?在咱们结束今天以前, 咱们应该看看一个Enzyme"渲染的界面 (在咱们的测试中, wrapper
的对象)。Enzyme文档 太棒了, 因此咱们要保持这个简短。
基本上, 当咱们使用find()
函数时, 咱们会将它传递给一个选择器, 它将返回一个ShallowWrapper
实例来包装找到的节点。find()
函数能够取字符串、函数或对象。
当咱们将字符串传递给find()
函数时, 咱们能够传递 CSS 选择器或组件的 _显示名称_。例如:
wrapper.find('div.link'); wrapper.find('Link')
咱们还能够将它传递给组件构造函数, 例如:
import { Link } from 'react-router'; // ... wrapper.find(Link)
最后, 咱们还能够传递对象属性选择器对象, 它经过键和值来选择元素。例如:
wrapper.find({to: '/login'});
返回值是一个 ShallowWrapper
, 它是一种ShallowWrapper
类型 (咱们能够渲染包装和浅包装)。这些 Wrapper
实例有一组功能, 咱们可使用这些函数来针对不一样的子组件, 查看 props
和 state
,的方法, 以及渲染的组件的其余属性, 如html()
和text()
。更甚的是, 咱们能够把这些调用串在一块儿。
以 <Link />
组件为例。若是咱们想找到基于全部可用连接的连接类的 HTML, 咱们能够编写这样的测试:
// ... it('displays a link tag with the Login text', () => { link = wrapper .find('Link') .find({to: '/login'}) expect(link.html()) .toBe('<a class="link">Login</a>') });
哦!今天有不少新的信息, 可是看看咱们是如何快速地用Enzyme来编写后续测试的。阅读的速度要快得多, 并且更容易辨别实际发生的事情。
明天, 咱们将继续咱们的测试旅程和经过集成测试测试咱们的应用。