## 四、Enzyme库
[Enzyme](https://github.com/airbnb/enzyme)是官方测试工具库的封装,它模拟了jQuery的API,非常直观,易于使用和学习。
它提供三种测试方法。
* `shallow`
* `render`
* `mount`
### 4.1 shallow
[shallow](https://github.com/airbnb/enzyme/blob/master/docs/api/shallow.md)方法就是官方的shallow rendering的封装。
下面是第一个[测试用例](https://github.com/ruanyf/react-testing-demo/blob/master/test/enzyme1.test.js#L6),测试`App`的标题。
~~~
import {shallow} from 'enzyme';
describe('Enzyme Shallow', function () {
it('App\'s title should be Todos', function () {
let app = shallow(<App/>);
expect(app.find('h1').text()).to.equal('Todos');
});
};
~~~
上面代码中,`shallow`方法返回`App`的浅渲染,然后`app.find`方法找出`h1`元素,`text`方法取出该元素的文本。
关于`find`方法,有一个注意点,就是它只支持简单选择器,稍微复杂的一点的CSS选择器都不支持。
~~~
component.find('.my-class'); // by class name
component.find('#my-id'); // by id
component.find('td'); // by tag
component.find('div.custom-class'); // by compound selector
component.find(TableRow); // by constructor
component.find('TableRow'); // by display name
~~~
### 4.2 render
[`render`](https://github.com/airbnb/enzyme/blob/master/docs/api/render.md)方法将React组件渲染成静态的HTML字符串,然后分析这段HTML代码的结构,返回一个对象。它跟`shallow`方法非常像,主要的不同是采用了第三方HTML解析库Cheerio,它返回的是一个Cheerio实例对象。
下面是第二个[测试用例](https://github.com/ruanyf/react-testing-demo/blob/master/test/enzyme1.test.js#L13),测试所有Todo项的初始状态。
~~~
import {render} from 'enzyme';
describe('Enzyme Render', function () {
it('Todo item should not have todo-done class', function () {
let app = render(<App/>);
expect(app.find('.todo-done').length).to.equal(0);
});
});
~~~
在上面代码中,你可以看到,`render`方法与`shallow`方法的API基本是一致的。 Enzyme的设计就是,让不同的底层处理引擎,都具有同样的API(比如`find`方法)。
### 4.3 mount
[`mount`](https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md)方法用于将React组件加载为真实DOM节点。
下面是第三个[测试用例](https://github.com/ruanyf/react-testing-demo/blob/master/test/enzyme1.test.js#L21),测试删除按钮。
~~~
import {mount} from 'enzyme';
describe('Enzyme Mount', function () {
it('Delete Todo', function () {
let app = mount(<App/>);
let todoLength = app.find('li').length;
app.find('button.delete').at(0).simulate('click');
expect(app.find('li').length).to.equal(todoLength - 1);
});
});
~~~
上面代码中,`find`方法返回一个对象,包含了所有符合条件的子组件。在它的基础上,`at`方法返回指定位置的子组件,`simulate`方法就在这个组件上触发某种行为。
下面是第四个[测试用例](https://github.com/ruanyf/react-testing-demo/blob/master/test/enzyme1.test.js#L28),测试Todo项的点击行为。
~~~
import {mount} from 'enzyme';
describe('Enzyme Mount', function () {
it('Turning a Todo item into Done', function () {
let app = mount(<App/>);
let todoItem = app.find('.todo-text').at(0);
todoItem.simulate('click');
expect(todoItem.hasClass('todo-done')).to.equal(true);
});
});
~~~
下面是第五个[测试用例](https://github.com/ruanyf/react-testing-demo/blob/master/test/enzyme1.test.js#L35),测试添加新的Todo项。
~~~
import {mount} from 'enzyme';
describe('Enzyme Mount', function () {
it('Add a new Todo', function () {
let app = mount(<App/>);
let todoLength = app.find('li').length;
let addInput = app.find('input').get(0);
addInput.value = 'Todo Four';
app.find('.add-button').simulate('click');
expect(app.find('li').length).to.equal(todoLength + 1);
});
});
~~~
### 4.4 API
下面是Enzyme的一部分API,你可以从中了解它的大概用法。
* `.get(index)`:返回指定位置的子组件的DOM节点
* `.at(index)`:返回指定位置的子组件
* `.first()`:返回第一个子组件
* `.last()`:返回最后一个子组件
* `.type()`:返回当前组件的类型
* `.text()`:返回当前组件的文本内容
* `.html()`:返回当前组件的HTML代码形式
* `.props()`:返回根组件的所有属性
* `.prop(key)`:返回根组件的指定属性
* `.state([key])`:返回根组件的状态
* `.setState(nextState)`:设置根组件的状态
* `.setProps(nextProps)`:设置根组件的属性