关于前端React组件测试(jest,Enzyme),网上有大量的入门文章,可以看看,但如果你确实想了解前端自动化测试,个人更推荐看官方的文档和一些比较官方的测试案列,这里推荐两个:

  1. enzyme官方文档,涵盖了各种说明和API;
  2. jest官方文档,涵盖了各种说明和API;
  3. antd基础组件库,每个组件都有较丰富的测试用例;

本篇文章适合对前端组件测试有一定概念的同学,本文将包含以下几点:

  • shallow, mount, render三种方法渲染的区别;
  • 组件事件的模拟及事件回调的mock;
  • 异步事件的模拟;

三种方法渲染的区别

组件测试最重要的前提,就是你需要知道怎么实例化自己的组件,然后才能去判断是否渲染正常,交互怎么样,功能是不是都OK,而这就和下面要说的渲染方法相关,了解一下三种方法的区别,有助于自己少掉坑,少抠脑壳,少掉头发,用一个关于antd Select组件的示例来说明。

function RenderTest() {return (<div className="render"><Select><Option key="1" value="1">test1</Option><Option key="2" value="2">test2</Option><Option key="3" value="3">test3</Option></Select></div>);
}
describe('base render test', () => {it('component mounted right', () => {const wrapper = mount(<RenderTest />);console.log('***mount***','Select:', wrapper.find('Select').length,'  Option:', wrapper.find('Option').length,'   Div', wrapper.find('div').length,'   class', wrapper.find('.render').length);});
});

在浏览器中,挂载这个RenderTest,渲染出来的DomTree是这样的:

然后在组件测试中分别用了三种方法来渲染,得到是下面的结果:

  • Shallow:与语义一样,肤浅表面的,也是浅渲染, 大概就是组件长啥样,就渲染成啥样,子组件不会递归渲染;
  • Mount: 又称Full DOM rendering,组件层虚拟Dom与真实Dom的渲染,所以这个方法里你既能看到Select节点(为2与组件的定义有关),Option为0,因为Antd的Option都是以绝对定位的方式挂载在body节点下的,而非wrapper节点里。你还可以打印wrapper.find('Trigger')的长度,结果为1,至于为什么,和Select为2一样,需要去看Select的源码;
  • Render: 又称静态渲染,真实dom节点的渲染,无虚拟Dom,不过有趣的是wrapper节点就是根节点,所以wrapper.find('.render')的length为0,而且很多前两者能用的方法在这里都没有,比如containsMatchingElement这样最基本的方法。

组件事件的模拟及事件回调的mock

下面两节都将以自己最近封装的一个Antd组件为例来做说明,在我前面的一篇文章里有提到,如下图所示:

这个组件的大致功能如上面图所示,产品需求就是需要一个编辑框,这个框在用户点击输入时,需要弹出一个搜索框,根据用户的输入远程搜索获取数据形成一个下拉列表,供用户选择,选择完成后搜索框被收起。而从代码上,也很简单:

<divclassName="originSearch"style={style}ref={el => this.searchInputWrapper = el}
><Inputref={e => this.searchInput = e}readOnlyplaceholder={placeholder}value={valueFormat(value)}style={{ width: '100%' }}size="default"{...inputProps}/>{isShowSearch &&<div className="js-origin-search origin-search"><Icon type="search" className="origin-search-icon" /><AutoCompleteautoFocusref={el => this.searchRealInput = el}className="certain-category-search"dropdownClassName="certain-category-search-dropdown"dropdownMatchSelectWidthonSearch={this.handleChange}onSelect={this.handleSelect}style={{ width: '100%' }}optionLabelProp="value">{loading ? [<Option key="loading" disabled><Spin spinning={loading} style={{ paddingLeft: '45%', textAlign: 'center' }} /></Option>] : options}</AutoComplete></div>}
</div>

第一个事件,用户开始输入,Input被单击,click事件捕捉,isShowSearch由false变为true, AutoComplete组件渲染,并自动获得焦点。
对于这一个测试,这是需要有用户交互的,和测试点击浏览器,所以我们需要用到模拟事件simulate,看下面代码:

  it('Input state change disable when click', () => {const wrapper = mount(<HInputSearch {...searchProps} />);wrapper.find('input').simulate('click');expect(wrapper.state('isShowSearch')).toEqual(true);const inputNodes = wrapper.find('input');expect(inputNodes.length).toEqual(2);});

除了模拟点击事件,还可以模拟输入框值的change事件,接着我们还可以检测这个变化是否触发了相应的方法,比如下面这段:

  it('Input state change disable when click', () => {const inputValue = 'change';const change = jest.spyOn(HInputSearch.prototype, 'handleChange'); // handleChange是在定义组件时,定义的一个原型方法const wrapper = mount(<HInputSearch {...searchProps} />);wrapper.find('input').simulate('click');expect(wrapper.state('isShowSearch')).toEqual(true);const inputNode = wrapper.find('.origin-search input');inputNode.simulate('change', { target: { value: inputValue } });expect(wrapper.find('input').get(1).props.value).toEqual(inputValue);expect(change).toBeCalledWith(inputValue); // 这里可以用toBeCalled检测是否调用,而使用toBeCalledWith除了检测是否调用,还可以检测是否正确的传参;});  

除了在原型上直接mock响应的方法,也可以直接在实例上,查找出某个节点利用jest.spyOn来检测某个方法是否被调用。在下一节还会继续对jest的函数mock进行说明。

异步请求的模拟

此次封装的案例组件我称之为远程搜索输入框,所以涉及到防抖与异步请求的发起,所以在Input框值变化时,首先是使用lodash的debounce函数防抖,然后发起请求。所以当我们进行触发后的流程测试时,比如异步请求是否被调用,返回值是否正常的被存入state,Option是否生成,这些统统没法立即执行测试,而是需要一段时间的等待再来判断,我们把这称之为异步测试。进行这个测试,先理一理思路:

  • 首先: 需要模拟一个异步请求;
  • 其次: 需要模拟获取数据后数据转换函数format;
  • 最后: 模拟一个异步任务,这个简单,用setTimeout就可以。

来看一下实现:

export const response = [{name: '李梅梅', id: 12,
}, {name: '徐雷雷', id: 13,
}, {name: 'james', id: 14,
}];
export default function fetch() {return new Promise(resolve =>setTimeout(() => {resolve(response);}, 5000));
}
const mockFetch = jest.fn(val => fetch(val));
const mockFormat = jest.fn(data => data.map(({ id, name }, index) => ({label: `${name}(${id})`,value: name,key: index
})));
const searchProps = {value: initValue,style: { width: '100%' },search: {keyword: undefined,},onSelect: mockSelect,format: mockFormat,  // 利用jest.fn() mock的fetchData: mockFetch, // 利用jest.fn() mock的
};it('Input state change disable when click', (done) => { // 异步测试必备const inputValue = 'change';const change = jest.spyOn(HInputSearch.prototype, 'handleChange'); // handleChange是在定义组件时,定义的一个原型方法const wrapper = mount(<HInputSearch {...searchProps} />);wrapper.find('input').simulate('click');const inputNode = wrapper.find('.origin-search input');inputNode.simulate('change', { target: { value: inputValue } });expect(change).toBeCalledWith(inputValue); // 这里可以用toBeCalled检测是否调用,而使用toBeCalledWith除了检测是否调用,还可以检测是否正确的传参;setTimeout(() => { // 模拟异步任务expect(mockFetch).toHaveBeenCalledWith({ keyword: inputValue });expect(mockFormat).toBeCalled();done();  // 这个done()很重要,会告诉这个异步测试是否完成}, 1000);});  

结合上面的实例可以看出,好像异步测试也不是很麻烦,就在测试用例中多了个测试完的回调函数done;异步请求和mock函数其实质都是用jest.fn直接包一下;异步任务可以直接用setTimeout或者setImmediate来模拟,当然也有文艺一点的写法,比如写一个通用的:

// 异步任务模拟
function mockPromises() {return new Promise(resolve => setTimeout(() => {resolve();}, yourTime));
}

结语

写完这一个组件,自己收获还是非常大的,并不是学了jest或者enzyme这么多API的使用,说实话,这个意义真不大。主要意义在于不自愿的去看了一些Antd组件以及Antd 底层组件的一些实现源码,它的高阶组件应用以及组件拆分的方式让我还是很有收获。另外,我其实一直在思考,写单元测试的意义,因为开始我以为这个很神奇,能够写一些逻辑什么的,就能找出自己组件的bug.其实不是,单测只是在你能想到的案列进行描述,然后看是否运行正常,有些小bug也许能找出来,但一些没想到的应用场景,bug还是在哪里,并没有被发现。所以我觉得意义不大,但后面一次优化,改变了我的想法。试想:

  • 当你歇了一段时间,也许你自己写的组件你都看不懂了,但你接到团队其他人的需求需要优化其中的一部分代码,你改完觉得没问题,就push了代码,但是一上线,发现动了不改动的逻辑,影响了最初的功能。这个时候,单元测试的意义就体现了。你第一次写了这个组件,并且写了相应的单元测试,并且代码测试覆盖率能达到80%,当你下一次优化时,优化完你只需要再跑一遍以前写的单元测试(如果是功能性的优化,有必要针对这个补充一个针对性的单元测试),如果测试跑通,你在push代码,这样发生bug的概率会显著下降。

以上就是本篇文章所有,谢谢浏览。有什么描述不严谨之处,还请指正。
原文见:地址
源码及测试用例:地址

怎样写一个具有异步交互的React组件的单元测试相关推荐

  1. 用python写一个识别仪表读数的AI组件

    要写一个识别仪表读数的AI组件,需要以下步骤: 收集数据集:首先需要收集大量的仪表读数图像,并为其标注读数数字. 建立模型:然后可以使用深度学习技术,例如卷积神经网络 (CNN) 或循环神经网络 (R ...

  2. 写一个高性能的敏感词检测组件

    最近写了一个高性能的敏感词检测组件[ToolGood.Words]. 一.高性能,它的效率到底有多快? 如果将正则表达式的算法效率设为1,高性能可达到正则表达式的1.5万倍. 二.选一个巧妙的算法: ...

  3. 对 React 组件进行单元测试(unit testing)

    2019独角兽企业重金招聘Python工程师标准>>> 在这里说一下前端开发的一个特点是更多的会涉及用户界面,当开发规模达到一定程度时,几乎注定了其复杂度会成倍的增长. 无论是在代码 ...

  4. 写一个构建复杂数据的日历组件 Kalendar

    需求:我们经常会在一些旅游.订票.酒店的页面中看到一些日历,这些日历有公历.农历.节假日.非节假日(调休)的标注,同时还有产品业务上的信息,比如 票价.余票等等.那现在开始造造轮子了,首先这个组件应该 ...

  5. 一个基于dumi搭建的react组件库,特别的开源组件项目,主要用于学习

    前言 在日常开发中,肯定会积累许许多多的业务组件在项目内,部分可以作为公共组件被抽离到公共组件库,但大部分或许与业务强相关,或许带有接口请求,并不适合抽离为公共组件,但仍需要有一个地方去展示这些组件的 ...

  6. 手把手教你用js写一个可以选择年月的动态日历组件

    啥话不说,我们先上效果图,源码放在最后 1.实现的功能 用年切换日历.用月切换日历, 可以显示用月显示,也可以用年显示,可以在日历中显示对应某天的时间 2.编写界面代码 <!DOCTYPE ht ...

  7. 如何在React Native中写一个自定义模块

    前言 在 React Native 项目中可以看到 node_modules 文件夹,这是存放 node 模块的地方,Node.js 的包管理器 npm 是全球最大的开源库生态系统.提到npm,一般指 ...

  8. 如何在 React Native 中写一个自定义模块

    前言 在 React Native 项目中可以看到 node_modules 文件夹,这是存放 node 模块的地方,Node.js 的包管理器 npm 是全球最大的开源库生态系统.提到npm,一般指 ...

  9. 解析Markdown文件生成React组件文档

    前言 最近做的项目使用了微前端框架single-spa. 对于这类微前端框架而言,通常有个utility应用,也就是公共应用,里面是各个子应用之间可以共用的一些公共组件或者方法. 对于一个团队而言,项 ...

最新文章

  1. OpenCV 【十三】矩阵的掩码操作
  2. mysql dba系统学习(1)mysql各版本编译安装
  3. 2.1 为什么要进行实例探究-深度学习第四课《卷积神经网络》-Stanford吴恩达教授
  4. python产生5个随机数_Python和numpy生成随机数
  5. 信息系统项目管理师-信息化与信息系统核心知识点思维脑图
  6. Python3.7模块之hashlib
  7. 友浩达优选上新,原生态农产品,买得安心,吃得放心
  8. 计算机专业考研过关率高么,考研过国家线的几率有多大?被刷的有多少?
  9. java service 事物_Service 事务(JdbcUtils 升级)
  10. CentOS7.4中搭建lnmp环境
  11. keyshot卡住了还能保存吗_Sketchup建模和渲染能取代3dsMax吗?
  12. 不能说的秘密-重载、重写与继承中的构造方法
  13. python 上传文件
  14. 台式计算机的质保期是多少,戴尔台式机保修多长时间
  15. 宏碁服务器u盘装系统,宏基u盘装系统教程图解
  16. 开源数值计算软件OCTAVE 、SCILAB、R、Matlab简介
  17. 自己开发了一个JsonViewer工具--FrogJson
  18. 怎么用计算机弹出光年之外,明日之后光年之外简谱代码-明日之后光年之外怎么弹-明日之后光年之外钢琴曲谱分享_牛游戏网...
  19. 《图解HTTP》--返回结果的HTTP状态码
  20. c++left right 和 setw() 函数的用法Alignment of Code

热门文章

  1. USACO Section2.2 Preface Numbering 解题报告 【icedream61】
  2. 【python】Python的基本数据类型之数字类型与字符串类型
  3. Kettle环境搭建及使用(数据迁移)
  4. web测试与app测试异同
  5. Jmeter中的几种协议
  6. 软件测试核心之用例设计
  7. python assert 用法_Python量化投资实战营大咖开讲!
  8. sql查询每科成绩的最高分_数据分析SQL查询:一文带你入门到掌握
  9. 【非科班告诉你】前端自学从小白到入门
  10. 前端学到什么水平就可以去找工作呢?