本文转载自:众成翻译
译者:iOSDevLog
链接:http://www.zcfy.cc/article/3806
原文:https://www.fullstackreact.com/30-days-of-react/day-25/
今天,我们将看看一个由Airbnb所维护的开源库,名为Enzyme,使得测试变得简单易用。
昨天我们使用了react-addons-test-utils
库来编写我们对Timeline
组件的第一个测试。但是, 此库是相当低级的, 使用起来可能有点麻烦。Enzyme是由 AirBnb 团队发布和维护的测试实用程序库, 它提供了一个更好的、高级的 API 来处理测试中的React组件。
我们在测试我们的
组件:
使用Enzyme
我们将使用Enzyme, 使这些测试更容易写和更可读。
昨天, 我们写了我们的第一个测试如下:
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( );
TestUtils
.findRenderedDOMComponentWithClass(wrapper, 'notificationsFrame');
});
})
虽然这是可行的, 但它不是世界上最容易阅读的测试。当用Enzyme我们重写它时让我们看看这个测试的样子。
我们可以只测试组件的输出, 而不是用Enzyme来测试完整的组件树。将不渲染任何组件的子级。这称为 浅 渲染。
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( )
> 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( );
expect(wrapper.find('.notificationsFrame').length).toEqual(1);
});
it('has a title of Timeline', () => {
wrapper = mount( )
expect(wrapper.find('.title').text()).toBe("Timeline")
})
describe('search button', () => {
let search;
beforeEach(() => wrapper = mount( ))
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( )
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( );
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( );
expect(wrapper.find('.notificationsFrame').length).toEqual(1);
});
it('has a title of Timeline', () => {
wrapper = mount( ) // 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( ))
beforeEach(() => search = wrapper.find('input.searchInput'))
// ...
})
})
若要测试是否隐藏了搜索输入, 我们只需要知道是否应用了active
类。Enzyme为我们提供了一种使用 hasClass()
方法检测组件是否有类的方法。让我们填写第一个测试, 期望搜索输入没有活动类:
describe('Timeline', () => {
let wrapper;
// ...
describe('search button', () => {
let search;
beforeEach(() => wrapper = mount( ))
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( )
// ...
})
})
现在, 我们可以测试ActivityItem
组件列表的长度。我们将设定我们的期望, 如果长度至少是4的名单。
describe('status updates', () => {
it('has 4 status updates at minimum', () => {
wrapper = shallow( )
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( );
expect(wrapper.find('.notificationsFrame').length).toEqual(1);
});
it('has a title of Timeline', () => {
wrapper = mount( )
expect(wrapper.find('.title').text()).toBe("Timeline")
})
describe('search button', () => {
let search;
beforeEach(() => wrapper = mount( ))
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( )
expect(
wrapper.find('ActivityItem').length
).toBeGreaterThan(3)
})
})
})
[](#whats-the-deal-with-find)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()
。更甚的是, 我们可以把这些调用串在一起。
以 组件为例。如果我们想找到基于所有可用链接的链接类的 HTML, 我们可以编写这样的测试:
// ...
it('displays a link tag with the Login text', () => {
link = wrapper
.find('Link')
.find({to: '/login'})
expect(link.html())
.toBe('Login')
});
哦!今天有很多新的信息, 但是看看我们是如何快速地用Enzyme来编写后续测试的。阅读的速度要快得多, 而且更容易辨别实际发生的事情。
明天, 我们将继续我们的测试旅程和通过集成测试测试我们的应用。