Toggle navigation
痞子棠
主页
About Me
归档
标签
痞子棠
一个不会钓鱼的程序员不是一个好设计师
Enzyme笔记
无
2017-03-06 15:01:42
1939
1
1
wangying6412
-------------------- #声明 + 这是一篇学习笔记,不是教程 + 本笔记只对作者自己负责 + 不保证所有内容均为原创 + 当前笔记针对 enzyme@2.7.1 ------------------- #前言 然而... 我不知道我在这写东西到底有没有人看,如果您感觉这些东西对您有用,麻烦点个赞吧,让我知道一下,这样子我也有动力来更新了。 <br /> 注意 : *你会发现有些示例代码使用 mocha,有些使用jest,这是因为我感觉有些代码不需要验证,所以直接从官方贴过来了,而官方代码是用的mocha,凡是使用jest的,都是我验证过的。这两种测试框架差别并不大,所以这不是什么问题。* ------------------- #安装 ``` npm install enzyme --save-dev npm install react-addons-test-utils --save-dev npm install react-dom --save-dev ``` #3种渲染方法 ##浅渲染 shallow() >浅渲染在将一个组件作为一个单元进行测试的时候非常有用,可以确保你的测试不会去间接断言子组件的行为。shallow 方法只会渲染出组件的第一层 DOM 结构,其嵌套的子组件不会被渲染出来,从而使得渲染的效率更高,单元测试的速度也会更快。 ``` import { shallow } from 'enzyme' describe('Enzyme Shallow', () => { it('App should have three <Todo /> components', () => { const app = shallow(<App />) expect(app.find('Todo')).to.have.length(3) }) } ``` ##深度渲染 mount() **!不支持 react-native** >mount 方法则会将 React 组件渲染为真实的 DOM 节点,特别是在你依赖真实的 DOM 结构必须存在的情况下,比如说按钮的点击事件。完全的 DOM 渲染需要在全局范围内提供完整的 DOM API, 这也就意味着它必须在至少“看起来像”浏览器环境的环境中运行。 ``` import { mount } from 'enzyme' describe('Enzyme Mount', () => { it('should delete Todo when click button', () => { const app = mount(<App />) const todoLength = app.find('li').length app.find('button.delete').at(0).simulate('click') expect(app.find('li').length).to.equal(todoLength - 1) }) }) ``` ##静态渲染 render() >render 方法则会将 React 组件渲染成静态的 HTML 字符串,返回的是一个 Cheerio 实例对象,采用的是一个第三方的 HTML 解析库 Cheerio,官方的解释是「我们相信 Cheerio 可以非常好地处理 HTML 的解析和遍历,再重复造轮子只能算是一种损失」(*吐槽:所以说这不是你家的轮子,所以有BUG你也不管是吧?*)。这个 CheerioWrapper 可以用于分析最终结果的 HTML 代码结构,它的 API 跟 shallow 和 mount 方法的 API 都保持基本一致。 ``` import { render } from 'enzyme' describe('Enzyme Render', () => { it('Todo item should not have todo-done class', () => { const app = render(<App />) expect(app.find('.todo-done').length).to.equal(0) expect(app.contains(<div className="todo" />)).to.equal(true) }) }) ``` ------------------------ #API ##.find() > 根据选择器(selector)查找节点 ``` const wrapper = shallow(<MyComponent />); expect(wrapper.find('.foo')).to.have.length(1); expect(wrapper.find('.bar')).to.have.length(3); // compound selector expect(wrapper.find('div.some-class')).to.have.length(3); // CSS id selector expect(wrapper.find('#foo')).to.have.length(1); ``` ##.findWhere(fn) > 根据自定义条件查找所有节点 ``` const wrapper = shallow(<MyComponent />); const complexComponents = wrapper.findWhere(n => n.type() !== 'string'); expect(complexComponents).to.have.length(8); ``` ##.at(index) 及 .get(index) >at() 返回一个渲染过的对象 >get() 返回一个react node,要测试它,得重新渲染。 ``` //假设 MyComponent.js <View><View><Text>hello</Text></View></View> //MyComponent.test.js const wrapper = shallow(<MyComponent />); //at() let text = wrapper.find('Text').at(0).props().children; //get() let node = wrapper.find('Text').get(0); //返回<Text /> let text_wrapper = shallow(node);//重新渲染 let text2 = text_wrapper.find('Text').at(0).props().children; expect(text).toBe(text2); expect(text).toBe(node.props.children);//也可以不渲染直接取props ``` ##.childAt(index) 及 .children() > .childAt(index) 返回一个渲染过的当前wrapper的index索引处的子对象 > .children() 字面意思,返回子对象数组 ## .closest(selector) > 通过遍历树中当前节点的祖先,从自身开始,返回与选择器匹配的第一个元素的wrapper。 ##.contains(nodeOrNodes) > 当前对象是否包含“参数”中的node > 接受参数为 react 对象 或者 react对象数组 > 返回 `true || false` ``` const wrapper = shallow( <div> <span>Hello</span> <div>Goodbye</div> <span>Again</span> </div> ); expect(wrapper.contains([ <span>Hello</span>, <div>Goodbye</div>, ])).to.equal(true); expect(wrapper.contains([ <span>Hello</span>, <div>World</div>, ])).to.equal(false); ``` ##containsAllMatchingElements(nodes) > wrapper的内容是否包含参数中的所有react节点 > 与contains()类似,但是只接受数组作为参数 > 示例与.contains()差不多 ## .matchesElement(node) > wrapper的内容是否匹配参数中的node > 只接受单个node ##.containsAnyMatchingElements(nodes) > 字面意思,应该好理解,contains与参数中任何一个node匹配,就返回`true` ## .containsMatchingElement(node) > 与.contains() 类似,但是只接受单个reactNode作为参数 ##.context(key) > 返回当前wrapper的上下文 ``` const wrapper = shallow( <MyComponent />, { context: { foo: 10 } } ); expect(wrapper.context().foo).to.equal(10); expect(wrapper.context('foo')).to.equal(10); ``` ## .debug() 及 .html() > .debug() 返回类似于 HTML 的字符串 > .html() 返回当前渲染树的渲染HTML字符串。 > 注意:只能在单个节点上使用.html()。 > .html()返回的内容与.debug()稍有不同 。 ## **.dive()** - !!!重点 > 浅渲染当前wrapper中的一个复合子组件 > 与.shallow()是有区别的,下方有记录 ``` test('试验',()=>{ class Bar extends React.Component { render() { return ( <div> <div className="in-bar" /> </div> ); } } class Foo extends React.Component { render() { return ( <div> <Bar /> </div> ); } } const wrapper = shallow(<Foo />); //因为浅渲染,Bar 在当前wrapper中只会渲染成 <Bar />,所以找不到其包含的 '.in-bar' expect(wrapper.find('.in-bar').length).toBe(0); //这里找到的 Bar 是 <Bar /> expect(wrapper.find(Bar).length).toBe(1); //对'<Bar />'使用dive()后,将会渲染它,然后就可以找到'.in-bar'了 expect(wrapper.find(Bar).dive().find('.in-bar').length).toBe(1); //自己看输出的区别 console.log(wrapper.debug()); console.log(wrapper.find(Bar).dive().debug()); }); ``` ## **.shallow()** !!!重点 > 浅渲染当前节点并返回一个wraper。 > **与dive()的区别:** > dive() 的目标只能是非dom reactElement(不能是div span 等) > shallow() 的目标只能是单独的wrapper(即浅渲染对象,可以是div span) ``` //例如,在上面shallow的例子中使用如下代码 console.log(wrapper.find(Bar).dive().find('.in-bar').dive().debug()); //报错,不能对dom使用 console.log(wrapper.find(Bar).dive().find('.in-bar').shallow().debug()); //正常输出html ``` ## **.render()** !!!重点 > 对当前节点的使用Cheerio渲染,并返回CheerioWrapper (也就是静态渲染当前节点) > 附: *cheerio 好像有bug,会报“props非法”的警告* ##.every(selector) && .everyWhere(fn) > .every() 检查所有节点是否与参数中的选择器匹配。 > .everyWhere() 参数为函数 ``` const wrapper = shallow( <div> <div className="foo qoo" /> <div className="foo boo" /> <div className="foo hoo" /> </div> ); expect(wrapper.find('.foo').every('.foo')).to.equal(true); expect(wrapper.find('.foo').every('.qoo')).to.equal(false); expect(wrapper.find('.foo').every('.bar')).to.equal(false); ``` ## .filter(selector) && .filterWhere(fn) > 过滤,从wrapper中筛选出与参数中的selector匹配的节点 ``` const wrapper = shallow(<MyComponent />); expect(wrapper.find('.foo').filter('.bar')).to.have.length(1); ``` ## .some(selector) && .someWhere(fn) > wrapper中是否有任何节点与参数中的selector匹配。 > 返回 `true || false` ``` const wrapper = shallow( <div> <div className="foo qoo" /> <div className="foo boo" /> <div className="foo hoo" /> </div> ); expect(wrapper.find('.foo').some('.qoo')).to.equal(true); expect(wrapper.find('.foo').some('.foo')).to.equal(true); expect(wrapper.find('.foo').some('.bar')).to.equal(false); ``` ## .instance() > 获取wrapper传递到shallow()的实例 ``` const wrapper = shallow(<MyComponent />); const inst = wrapper.instance(); expect(inst).to.be.instanceOf(MyComponent); ``` ## .is(selector) && .not(selector) > wrapper是否与selector匹配 > not() = !is() > 返回 `true || false` ``` const wrapper = shallow(<div className="some-class other-class" />); expect(wrapper.is('.some-class')).to.equal(true); ``` ## .key() > 获取wrapper的key属性 ## .name() > 获取wrapper的displayName ``` const wrapper = shallow(<div/>); expect(wrapper.name()).to.equal('div'); const SomeWrappingComponent = () => <Foo />; const wrapper = shallow(<SomeWrappingComponent />); expect(wrapper.name()).to.equal('Foo'); Foo.displayName = 'A cool custom name'; const SomeWrappingComponent = () => <Foo />; const wrapper = shallow(<SomeWrappingComponent />); expect(wrapper.name()).to.equal('A cool custom name'); ``` ## .exists() > 检测wrapper是否存在 > 返回 `true || false` ``` const wrapper = shallow(<div className="some-class" />); expect(wrapper.find('.other-class').exists()).to.be(false); ``` ## .state([key]) > 获取state ## .setContext(context) > 设置wrapper的上下文 ## .setProps(props) > 设置wrapper的props > 这个东西非常有用,为测试提供了不少便利 ## .setState(state[, callback]) !!!注意 > 设置wrapper的state,第二个参数接受一个callback > 虽然它有callback但是好像并非异步,所以说这个callback只是个摆设?? > 是个好东西,但是官方说不要滥用。据说它可能不太准确 :) > 注意:只能在根wrapper上调用。 ``` test('试验',()=>{ //Foo class Foo extends React.Component { constructor(props) { super(props); this.state = { name: 'foo' }; } render() { return ( <div className={this.state.name}/> ); } } //test const wrapper = shallow(<Foo />); expect(wrapper.find('.foo').length).toBe(1); expect(wrapper.find('.bar').length).toBe(0); wrapper.setState({ name: 'bar' }); //注意这里并没有使用回调,这说明setState并非异步 expect(wrapper.find('.foo').length).toBe(0); expect(wrapper.find('.bar').length).toBe(1); }); ``` ## .slice([begin[, end]]) > 删除节点 ``` const wrapper = shallow( <div> <div className="foo bax" /> <div className="foo bar" /> <div className="foo baz" /> </div> ); expect(wrapper.find('.foo').slice(1)).to.have.length(2); expect(wrapper.find('.foo').slice(1).at(0).hasClass('bar')).to.equal(true); expect(wrapper.find('.foo').slice(1).at(1).hasClass('baz')).to.equal(true); expect(wrapper.find('.foo').slice(1, 2)).to.have.length(1); expect(wrapper.find('.foo').slice(1, 2).at(0).hasClass('bar')).to.equal(true); ``` ## .type() > 返回此wrapper的节点类型。 如果它是一个复合组件,这将是组件构造函数。 如果它是本地DOM节点,它将是一个标签名称的字符串。 如果它为`null`,它依然是`null` > 返回 :`String|Function|null` ``` class Foo extends React.Component { render() { return <div />; } } const wrapper = shallow(<Foo />); expect(wrapper.type()).to.equal('div'); ``` ## **.simulate(event)** !!!重要 > 模拟事件 > 动作机制为将参数中的 'event'首字母大写后在前面加上'on' 变为 'onEvent' 然后检查props中是否有onEvent,并运行 > 如下方示例中的 onCustomEvent ``` test('试验',()=>{ class Foo extends React.Component { constructor(props) { super(props); this.state = { count: 0, size : 0, }; } render() { const { count,size } = this.state; return ( <div> <div className={`clicks-${count}`}> {count} clicks </div> <a onClick={() => this.setState({ count: count + 1 })} onCustomEvent={() => this.setState({ size : size + 10 })} > Increment </a> </div> ); } } const wrapper = shallow(<Foo />); expect(wrapper.find('.clicks-0').length).toBe(1); wrapper.find('a').simulate('click'); expect(wrapper.find('.clicks-1').length).toBe(1); wrapper.find('a').simulate('click'); expect(wrapper.find('.clicks-2').length).toBe(1); expect(wrapper.find('.clicks-2').text()).toBe('2 clicks'); wrapper.find('a').simulate('onCustomEvent'); expect(wrapper.state('size')).toBe(0); //这里size=0 ,因为参数onCustomEvent会被译为onOnCustomEvent wrapper.find('a').simulate('customEvent'); expect(wrapper.state('size')).toBe(10); //这里正确触发了onCustomEvent }); ``` ## .tap(intercepter) > 拦截器,参数为函数,会传递wrapper本身。并且返回wrapper本身 ``` test('试验',()=>{ const result = shallow( <ul> <li>xxx</li> <li>yyy</li> <li>zzz</li> </ul> ).find('li') .tap(n => { console.log(n.at(0).name(); // 'li' })) .map(n => n.text()); console.log(result); //[ 'xxx', 'yyy', 'zzz' ] }); ``` ## **.unmount()** !!! > 卸载wrapper > 可以用来模拟 componentWillUnmount ``` const spy = sinon.spy(); //sinon 是另一个第三方组件,可以用来测试事件的触发,非常方便 //也可以使用 jest.spyOn(object, methodName)来代替 class Foo extends React.Component { constructor(props) { super(props); this.componentWillUnmount = spy; } render() { return ( <div className={this.props.id}> {this.props.id} </div> ); } } const wrapper = shallow(<Foo id="foo" />); expect(spy.calledOnce).to.equal(false); wrapper.unmount(); expect(spy.calledOnce).to.equal(true); ``` ## .update() !!!bug > 强制重新渲染。 > 在检查渲染输出前运行有用,如果外部事物可能更新组件的状态。 *注意:实测未通过,该api可能无效。也可能我的测试方法不正确。* ``` test('试验',()=>{ class ImpureRender extends React.Component { constructor(props) { super(props); this.count = 0; } render() { return <div>{this.count++}</div>; } } const wrapper = shallow(<ImpureRender />); expect(wrapper.text()).toBe('0'); wrapper.update(); expect(wrapper.text()).toBe('1'); //测试未通过 wrapper.update().update().update(); console.log(wrapper.text()); //0 }); ``` ##无需笔记的API + .equals(node) + .first() + .last() + .parent() + .parents([selector]) + .forEach(fn) + .hasClass(className) + .map(fn) + .prop(key) + .props() + .reduce() + .text() -------------------------------- #选择器selector ## 1. CSS选择器 *与jQuery差不多,没什么好记的* ``` wrapper.find('.foo') //类选择器 wrapper.find('input') //标签选择器 wrapper.find('#foo') //ID选择器 wrapper.find('[htmlFor="foo"]') //属性选择器 ``` ## 2. 根据构造函数选择 ``` class MyComponent extends React.Component { render() { ... } } //find instances of MyComponent const myComponents = wrapper.find(MyComponent); ``` ## 3. 基于 React 的 displayName 来查找组件 *注意:这只有在选择器(以及组件的displayName)是以大写字母开头时才会工作。以小写字母开头会被当作标签选择器处理。* ``` class MyComponent extends React.Component { render() { ... } } MyComponent.displayName = 'MyComponent'; // find instances of MyComponent const myComponents = wrapper.find('MyComponent'); ``` ## 4. 根据属性的子集查找组件和节点 ``` const wrapper = mount( <div> <span foo={3} bar={false} title="baz" /> </div> ) wrapper.find({ foo: 3 }) wrapper.find({ bar: false }) wrapper.find({ title: 'baz'}) ``` #sinon [http://sinonjs.org/](http://sinonjs.org/) > 一个强大的测试辅助工具,这里仅简单介绍,具体使用方法见其官方网站。 ##安装 npm install sinon --save-dev ##使用方法(简要) ``` const willMount = sinon.spy(); const didMount = sinon.spy(); const willUnmount = sinon.spy(); class Foo extends React.Component { constructor(props) { super(props); this.componentWillUnmount = willUnmount; this.componentWillMount = willMount; this.componentDidMount = didMount; } render() { return ( <div className={this.props.id}> {this.props.id} </div> ); } } const wrapper = mount(<Foo id="foo" />); expect(willMount.callCount).to.equal(1); expect(didMount.callCount).to.equal(1); expect(willUnmount.callCount).to.equal(0); wrapper.unmount(); expect(willMount.callCount).to.equal(1); expect(didMount.callCount).to.equal(1); expect(willUnmount.callCount).to.equal(1); ``` ------------------------ #对react-native的测试 注意:不支持mount() 可以安装react-native-mock,以模拟react-native组件 ``` npm i --save-dev react-native-mock ``` ##杂记 + 在test中使用console时,会触发一些警告。(我怀疑是因为Cheerio对react-native模拟不完善,当然,并没有深究这个问题。只是一些警告,忽略掉就好。) + 与react-test-renderer一起使用时会有一些冲突 ---------------------- ``` ```
上一篇: 无
下一篇:
Jest笔记
1
赞
1939 人读过
新浪微博
微信
更多分享
腾讯微博
QQ空间
人人网
提交评论
立即登录
, 发表评论.
没有帐号?
立即注册
1
条评论
More...
文档导航
没有帐号? 立即注册