最近在项目里尝试用React Testing Library写测试,从入门到差点放弃,经历了很多坑,今天分享一下我的真实经历和踩坑记录。

最近,我,在,项目,里,尝试,引入,React Testing Library,来,给,React,组件,写,单元测试,本来,以为,是,一件,比较,简单,的,事情,毕竟,之前,也,用,过,Enzyme,写,过,测试,觉得,换,个,测试,库,应该,不,难,但是,真正,开始,用,了,之后,才,发现,完全,不是,那么,回事,React Testing Library,的,理念,和,用法,和,Enzyme,差别,很,大,而且,有,很多,坑,我,从,入门,到,差点,放弃,经历,了,很多,折腾,最后,虽然,慢慢,上手,了,但是,也,走,了,很多,弯路,今天,这篇,文章,就,来,分享,一下,我,使用,React Testing Library,的,真实,经历,和,踩坑,记录,以及,我,对,这个,测试,库,的,一些,看法,和,建议,希望,能,给,同样,在,尝试,或者,准备,使用,React Testing Library,的,朋友,一些,参考,少,走,一些,弯路。

一、为什么选择React Testing Library

先,说说,我,为什么,会,选择,React Testing Library,而,不是,继续,用,Enzyme,其实,我,之前,的,项目,一直,用,的,是,Enzyme,来,写,React,组件,的,单元测试,用,了,很,多年,也,比较,熟悉,了,但是,Enzyme,有,一些,问题,让,我,一直,不太,满意,比如,Enzyme,的,shallow,render,和,mount,三种,渲染,方式,经常,让,人,混淆,不知道,该,用,哪,种,而且,shallow,渲染,不,会,渲染,子,组件,很多,时候,测试,不,到,真实,的,DOM,结构,和,交互,另外,Enzyme,的,API,比较,多,也,比较,复杂,学习,成本,不低,而且,很多,API,是,针对,React,内部,实现,的,比如,instance,state,props,等等,测试,的,时候,经常,需要,访问,组件,的,内部,状态,和,方法,这,其实,不,是,一种,好,的,测试,方式,因为,它,测试,的,是,组件,的,内部,实现,而,不是,组件,的,外部,行为,一旦,组件,的,内部,实现,变,了,哪怕,外部,行为,没,变,测试,也,会,挂,掉,这样,的,测试,很,脆弱,也,很,难,维护。

后来,我,在,网上,看,到,很多,人,推荐,React Testing Library,说,它,的,理念,更,先进,测试,方式,更,合理,也,更,容易,维护,我,就,去,了解,了,一下,React Testing Library,的,核心理念,是,“测试,你的,软件,的,使用,方式,而,不是,它,的,实现,细节,”,也就是说,它,鼓励,你,从,用户,的,角度,去,测试,组件,测试,用户,能,看到,什么,能,做,什么,而,不是,测试,组件,内部,的,state,props,方法,等等,这样,的,测试,更,接近,真实,的,使用,场景,也,更,稳定,更,容易,维护,因为,只要,组件,的,外部,行为,没,变,哪怕,内部,实现,重构,了,测试,也,不,会,挂,掉,这个,理念,一下,就,打动,了,我,因为,我,之前,用,Enzyme,写,测试,的,时候,确实,经常,遇到,组件,内部,重构,了,测试,就,大面积,挂,掉,的,情况,维护,起来,很,痛苦,所以,我,就,决定,在,新,项目,里,尝试,一下,React Testing Library,看看,是不是,真的,像,大家,说,的,那么,好,结果,这,一,尝试,就,经历,了,很多,坑,也,差点,放弃。

二、入门阶段:看起来很简单

刚开始,入门,的,时候,我,觉得,React Testing Library,还,挺,简单,的,按照,官方,文档,安装,配置,然后,写,了,第一个,测试,用例,比如,测试,一个,按钮,组件,渲染,出来,之后,能,显示,正确,的,文字,点击,之后,能,触发,回调,函数,代码,大概,是,这样,的,用,render,函数,渲染,组件,然后,用,screen.getByText,找到,按钮,元素,用,fireEvent.click,模拟,点击,然后,用,expect,断言,回调,函数,被,调用,了,整个,过程,确实,很,简单,也,很,直观,代码,量,也,不,多,而且,测试,的,确实,是,组件,的,外部,行为,而,不是,内部,实现,我,当时,还,挺,开心,的,觉得,这个,库,确实,不错,比,Enzyme,简单,也,更,合理,但是,很快,我,就,遇到,了,第一个,坑。

第一个,坑,是,getByText,找不到,元素,报错,我,写,了,一个,组件,里面,有,一段,文字,但是,用,getByText,找,的,时候,总是,报错,说,找不到,元素,我,检查,了,很,久,才,发现,原来,getByText,默认,是,精确,匹配,的,而且,会,忽略,元素,内部,的,其他,元素,比如,如果,文字,里面,嵌套,了,span,或者,其他,标签,getByText,就,找,不,到,了,需要,用,exact,参数,设,为,false,或者,用,正则,表达式,来,匹配,或者,用,getByText,的,函数,形式,来,自定义,匹配,规则,这个,问题,折腾,了,我,好,一会,才,解决,后来,我,才,知道,React Testing Library,的,查询,方法,有,很多,种,getBy,queryBy,findBy,还有,getAllBy,queryAllBy,findAllBy,每,种,的,行为,都,不,一样,比如,getBy,找,不,到,会,报错,queryBy,找,不,到,会,返回,null,findBy,是,异步,的,会,等待,元素,出现,这些,查询,方法,的,区别,和,使用,场景,需要,花,时间,去,理解,和,记忆,不然,很,容易,用,错,导致,测试,失败,或者,测试,不稳定,这,是,我,遇到,的,第一个,坑,也是,比较,基础,的,一个,坑。

三、进阶阶段:各种坑接踵而至

解决,了,基础,的,查询,问题,之后,我,开始,写,更,复杂,的,测试,用例,结果,各种,坑,接踵而至,让,我,应接不暇,也,差点,放弃,下面,说说,我,遇到,的,主要,的,几个,坑。

第一个,大坑,是,异步,代码,的,测试,我们,的,很多,组件,都,有,异步,逻辑,比如,组件,挂载,之后,请求,接口,获取,数据,然后,渲染,数据,这样,的,组件,用,React Testing Library,测试,的,时候,就,比较,麻烦,因为,数据,是,异步,获取,的,渲染,也,是,异步,的,你,不能,在,render,之后,立刻,去,断言,数据,已经,渲染,出来,了,需要,等待,异步,操作,完成,React Testing Library,提供,了,findBy,系列,的,查询,方法,是,异步,的,会,等待,元素,出现,还有,waitFor,函数,可以,等待,某个,条件,满足,这些,工具,确实,能,解决,异步,测试,的,问题,但是,用,起来,还是,有,很多,细节,需要,注意,比如,findBy,默认,的,超时,时间,是,1000毫秒,如果,异步,操作,比较,慢,就,会,超时,报错,需要,手动,设置,更长,的,超时,时间,再,比如,waitFor,里面,的,断言,如果,写,得,不,对,也,会,导致,测试,不稳定,或者,超时,另外,异步,测试,还,需要,配合,jest,的,fake timer,或者,真实,的,timer,来,处理,定时器,的,问题,比如,setTimeout,setInterval,等等,这些,都,有,很多,坑,我,当时,为了,测,一个,带,异步,请求,的,组件,折腾,了,整整,一天,才,把,测试,写,好,而且,中间,各种,报错,各种,不,稳定,差点,就,放弃,了,后来,还是,看,了,很多,官方,文档,和,网上,的,教程,才,慢慢,搞,明白,异步,测试,的,正确,姿势。

第二个,大坑,是,事件,模拟,的,问题,React Testing Library,用,fireEvent,来,模拟,用户,事件,比如,点击,输入,变化,等等,但是,fireEvent,和,真实,的,用户,事件,还是,有,区别,的,比如,fireEvent.click,只是,触发,了,click,事件,但是,不会,像,真实,用户,点击,那样,先,触发,mouseDown,mouseUp,等,事件,也,不会,触发,焦点,变化,等等,所以,有些,组件,的,逻辑,依赖,这些,事件,的,话,用,fireEvent,模拟,就,会,和,真实,行为,不一致,导致,测试,通过,但是,真实,环境,有,问题,或者,反过来,后来,React Testing Library,推出,了,userEvent,库,来,更,真实,地,模拟,用户,事件,比如,userEvent.click,会,模拟,更,真实,的,点击,过程,包括,mouseDown,mouseUp,click,以及,焦点,变化,等等,userEvent.type,会,模拟,更,真实,的,输入,过程,包括,每个,字符,的,输入,事件,等等,这个,库,确实,比,fireEvent,更,好,但是,也,需要,额外,安装,和,学习,而且,有些,场景,下,还是,有,问题,我,当时,为了,测,一个,输入框,组件,用,fireEvent.change,模拟,输入,结果,组件,里,的,一些,逻辑,没,触发,测试,不,通过,折腾,了,很,久,才,发现,是,因为,fireEvent.change,和,真实,输入,的,事件,序列,不一样,后来,换,了,userEvent.type,才,解决,这个,问题,这,也,让,我,意识到,虽然,React Testing Library,的,理念,是,测试,用户,的,真实,行为,但是,它,的,事件,模拟,工具,还是,和,真实,行为,有,差距,需要,注意,这些,细节,不然,很,容易,踩坑。

第三个,大坑,是,组件,依赖,的,mock,问题,很多,组件,会,依赖,一些,外部,模块,比如,请求,接口,的,API,模块,路由,的,模块,全局,状态,管理,的,模块,等等,测试,的,时候,需要,mock,这些,依赖,不然,测试,会,有,问题,比如,请求,真实,接口,导致,测试,慢,或者,不稳定,或者,依赖,路由,上下文,导致,渲染,报错,等等,React Testing Library,本身,不,提供,mock,的,功能,需要,配合,jest,的,mock,功能,来,做,但是,jest,的,mock,也,有,很多,坑,比如,mock,一个,模块,之后,怎么,恢复,怎么,针对,不同,测试,用例,设置,不同,的,mock,返回值,等等,另外,有些,组件,依赖,React Context,比如,ThemeContext,RouterContext,等等,测试,的,时候,需要,用,对应的,Provider,包裹,组件,不然,会,报错,或者,行为,不,对,比如,用,了,react-router,的,组件,测试,的,时候,需要,用,MemoryRouter,包裹,用,了,Redux,的,组件,测试,的,时候,需要,用,Provider,包裹,并且,提供,mock,的,store,这些,都,需要,额外,的,代码,和,配置,也,有,很多,细节,需要,注意,我,当时,测,一个,依赖,了,路由,和,接口,请求,的,页面,组件,为了,mock,路由,和,接口,折腾,了,很,久,才,搞,定,中间,各种,报错,各种,不,理解,差点,就,放弃,了,后来,还是,看,了,很多,例子,才,慢慢,搞,明白,怎么,正确,地,mock,这些,依赖。

第四个,大坑,是,测试,性能,的,问题,随着,测试,用例,越来越,多,我,发现,测试,运行,的,速度,越来越,慢,因为,React Testing Library,是,真实,渲染,组件,到,DOM,里,的,不像,Enzyme,的,shallow,渲染,那,么,轻量,所以,每个,测试,用例,的,开销,都,比较,大,而且,如果,组件,比较,复杂,渲染,时间,就,更,长,另外,异步,测试,需要,等待,也,会,增加,测试,时间,我,当时,项目,里,写,了,几十,个,测试,用例,运行,一次,要,好几,分钟,而且,有时候,还,会,因为,超时,或者,不,稳定,失败,需要,重跑,这,让,我,很,头疼,也,开始,怀疑,React Testing Library,是不是,真的,适合,我们,的,项目,后来,我,做,了,一些,优化,比如,合理,使用,queryBy,和,getBy,避免,不必要,的,异步,等待,比如,把,一些,通用,的,渲染,逻辑,抽,成,公共,函数,减少,重复,代码,比如,合理,设置,jest,的,并发,数,等等,才,把,测试,速度,提升,了,一些,但是,相比,Enzyme,的,shallow,渲染,还是,慢,不少,这,也,是,React Testing Library,的,一个,缺点,吧,毕竟,真实,渲染,的,开销,就是,比,浅渲染,大。

四、差点放弃的时刻

在,经历,了,上面,这些,坑,之后,有,一段,时间,我,真的,差点,就,放弃,了,觉得,React Testing Library,太,难用,了,坑,太,多,了,写,测试,的,效率,很,低,而且,测试,还,不,稳定,经常,莫名其妙,地,失败,需要,花,很多,时间,去,调试,我,甚至,开始,怀念,Enzyme,虽然,Enzyme,也,有,问题,但是,至少,我,熟悉,了,知道,怎么,用,也,知道,怎么,避开,那些,坑,而,React Testing Library,对,我,来说,是,全新,的,一切,都,要,重新,学,重新,踩坑,这个,过程,真的,很,痛苦,也,很,打击,信心。

但是,后来,我,还是,没有,放弃,主要,有,几个,原因,第一,是,我,确实,认可,React Testing Library,的,理念,测试,用户,行为,而,不是,实现,细节,这个,理念,是,对,的,也,是,未来,的,趋势,我,相信,只要,坚持,用,下去,熟悉,了,之后,肯定,能,带来,好处,第二,是,我,看到,越来越,多,的,项目,和,团队,都,在,从,Enzyme,迁移,到,React Testing Library,官方,也,在,推荐,这个,库,说明,它,确实,是,有,优势,的,也,是,值得,学习,的,第三,是,我,觉得,遇到,困难,就,放弃,不是,一个,好,的,习惯,很多,技术,都是,这样,入门,的,时候,比较,难,坑,比较,多,但是,一旦,上手,了,就,会,觉得,很,好用,也,很,高效,所以,我,决定,再,坚持,一段,时间,好好,学,一下,把,这些,坑,都,踩,过去,看看,到底,能,不,能,用好,React Testing Library。

于是,我,花,了,几个,晚上,的,时间,认真,看,了,React Testing Library,的,官方,文档,包括,核心,概念,查询,方法,异步,测试,事件,模拟,常见,问题,等等,还,看,了,很多,网上,的,教程,和,最佳实践,文章,以及,一些,开源,项目,里,的,测试,代码,学习,别人,是,怎么,用,的,慢慢,的,我,对,React Testing Library,的,理解,越来越,深入,了,也,慢慢,掌握,了,正确,的,使用,方法,之前,遇到,的,很多,坑,也,都,知道,怎么,避开,了,写,测试,的,效率,也,慢慢,提升,了,测试,也,越来越,稳定,了,这个,时候,我,才,慢慢,体会,到,React Testing Library,的,好处,也,觉得,之前,的,坚持,是,值得,的。

五、现在的使用感受和建议

现在,我,已经,在,项目,里,用,React Testing Library,写,了,不少,测试,了,也,基本,上手,了,对,这个,库,也,有,了,更,全面,的,认识,下面,说说,我,现在,的,使用,感受,以及,给,准备,使用,或者,刚,开始,使用,的,朋友,的,一些,建议。

首先,说,说,优点,第一,理念,先进,测试,用户,行为,而,不是,实现,细节,这样,的,测试,更,稳定,更,容易,维护,组件,内部,重构,不,会,导致,测试,大面积,失败,这,确实,是,很大,的,优势,第二,API,简洁,核心,API,不,多,render,screen,fireEvent,waitFor,等等,熟悉,了,之后,用,起来,很,顺手,也,很,直观,第三,社区,活跃,生态,好,现在,React Testing Library,已经,是,React,测试,的,主流,方案,了,社区,很,活跃,文档,也,很,完善,遇到,问题,很,容易,找到,答案,而且,很多,第三方,库,都,提供,了,对应,的,Testing Library,工具,比如,React Router,Redux,等等,集成,起来,比较,方便,第四,强制,好,的,测试,实践,因为,React Testing Library,不,鼓励,访问,组件,内部,实现,所以,会,强制,你,从,用户,的,角度,去,写,测试,这,其实,是,在,帮,你,养成,好,的,测试,习惯,长期,来看,是,有,好处,的。

然后,说,说,缺点,第一,入门,有,一定,门槛,虽然,核心,API,不,多,但是,各种,查询,方法,的,区别,异步,测试,的,处理,事件,模拟,的,细节,等等,都,需要,花,时间,去,学习,和,理解,刚,开始,用,的,时候,容易,踩坑,也,容易,有,挫败感,第二,测试,性能,相对,较差,因为,是,真实,渲染,组件,到,DOM,里,所以,每个,测试,用例,的,开销,比较,大,测试,数量,多,了,之后,运行,速度,会,比较,慢,需要,做,一些,优化,第三,有些,场景,测试,起来,比较,麻烦,比如,需要,访问,组件,内部,状态,或者,方法,的,场景,虽然,不,推荐,这样,做,但是,有些,特殊,场景,下,确实,需要,这,时候,用,React Testing Library,就,比较,麻烦,不如,Enzyme,方便,不过,这样,的,场景,比较,少,大部分,情况,下,还是,应该,从,用户,行为,的,角度,去,测试。

最后,给,准备,使用,或者,刚,开始,使用,的,朋友,一些,建议,第一,先,认真,看,官方,文档,理解,核心理念,和,基本,用法,不要,上来,就,凭,经验,写,不然,很,容易,走,弯路,也,很,容易,踩坑,官方,文档,写,得,很,好,也,很,详细,认真,看,一遍,能,避免,很多,问题,第二,从,简单,的,组件,开始,练手,不要,一上来,就,测,复杂,的,页面,组件,先,测,一些,简单,的,按钮,输入框,展示,组件,等等,熟悉,基本,的,渲染,查询,事件,模拟,等,用法,然后,再,慢慢,挑战,更,复杂,的,组件,第三,善用,社区,资源,遇到,问题,多,搜,一搜,很多,问题,都,是,常见,问题,网上,有,现成,的,解决方案,也,可以,看,一些,开源,项目,的,测试,代码,学习,别人,的,最佳实践,第四,要有,耐心,不要,因为,一开始,踩坑,多,就,放弃,任何,新,技术,都,有,一个,学习,曲线,React Testing Library,也,不,例外,只要,坚持,用,一段,时间,熟悉,了,之后,就,会,觉得,很,好用,也,很,高效,第五,合理,选择,测试,方案,不是,说,用,了,React Testing Library,就,完全,不能,用,其他,测试,工具,了,有些,场景,下,配合,其他,工具,效果,更,好,比如,端到端,测试,可以,用,Cypress,或者,Playwright,单元测试,用,React Testing Library,这样,搭配,起来,效果,更,好,不要,迷信,单一,工具,要,根据,实际,场景,选择,合适,的,工具。

六、写在最后

React Testing Library,对,我,来说,是,一个,让,我,又,爱,又,恨,的,工具,爱,的,是,它,的,理念,先进,测试,方式,合理,写,出来,的,测试,更,稳定,更,容易,维护,恨,的,是,入门,的,时候,坑,太,多,了,学习,曲线,比较,陡,让,我,差点,放弃,但是,现在,回头,看,我,很,庆幸,自己,当时,没有,放弃,坚持,了,下来,因为,现在,我,确实,体会,到,了,React Testing Library,的,好处,也,觉得,它,是,一个,值得,学习,和,使用,的,测试,工具。

当然,我,也,不是,说,React Testing Library,就是,完美,的,或者,所有,项目,都,应该,用,它,每个,工具,都,有,自己,的,适用,场景,和,优缺点,大家,还是,要,根据,自己,项目,的,实际,情况,和,团队,的,技术栈,来,选择,合适,的,测试,方案,不要,盲目,跟风,但是,如果,你,正在,考虑,给,React,项目,写,单元测试,或者,想,从,Enzyme,迁移,到,更,先进,的,测试,方案,那么,React Testing Library,确实,是,一个,值得,考虑,的,选择,只要,你,愿意,花,时间,去,学习,和,适应,它,肯定,能,给,你,带来,好处。

最后,希望,这篇,文章,能,给,同样,在,使用,或者,准备,使用,React Testing Library,的,朋友,一些,参考,和,帮助,少,走,一些,弯路,也,欢迎,大家,在,评论,区,分享,你们,使用,React Testing Library,的,经历,和,踩坑,记录,或者,有,什么,好,的,经验,和,技巧,也,可以,分享,出来,一起,交流,一起,学习,一起,进步,谢谢,大家,的,阅读。