一、前言
经过前期对前端自动化测试的充分调研,决定采用React官方推荐的Jest测试库对我们的React项目进行单测实践。
本以为按照官方文档,一顿安装、运行,再写几个测试用例就over,没想到的是,遇到的bug比吃过的米饭还多(手动狗头。)
对于前端单元测试,过往的项目没有使用过,问过身边的朋友和同事,也都没有写过。
国内文档大多数停留在官方文档的翻译版本和 1+1 应该等于 2 这样的初级单测案例。
有时候遇到问题,一卡卡一天,心态逐渐崩了...
庆幸的是,每次都在“痛定思痛”之后,冷静下来分析,常常会出现“山重水复疑无路,柳暗花明又一村”之感。
废话不多说,先将遇到的问题总结整理下来,也算前车之鉴,之后其它系统如果要接入Jest单测也可以避免踩同样的坑,节约大家开发时间。
二、问题总结
前置条件:
1、我们的项目React版本是17.0.1,是基于 create-react-app v4 创建的(Tips: create-react-app v4当前已经停止使用了,最低create-react-app v5版本可用);
2、前期直接按照 script命令:"test": "test" 运行的单测;
3、我们的Jest配置文件是放在package.json中(单独起文件jest.config.js存放效果一致)。
2.1、test测试文件位置的放置,导致的报错
解决方案: test目录一定要放在项目的src文件夹中,否则无论怎么修改Jest的配置文件的testMatch字段都会匹配不成功。
注意点:
1、安装 react-scripts 后,这部分不需要单独在Jest中配置;
2、script命令也改成:"react-scripts test"
2.2、Jest无法识别webpack alias(路径简写)
虽然我们在tsconfig.json 里面已经配置了相关的paths配置,但是Jest不认,Jest根本不管ts,所以在tsconfig配置的paths, 在Jest配置里需要再配一次。
不过可以借助 ts-jest 里的工具函数 pathsToModuleNameMapper 来把 tsconfig.json 里的 paths 配置复制到 jest.config.js 里的 moduleNameMapper
解决方案: 因为我们项目Jest配置文件内置在Package.json中,所以,我手动去配置了项目中使用的所有路径映射。
"jest": {
"moduleNameMapper": {
"^@/(.*)$": "<rootDir>/src/$1",
"^Assets/(.*)$": "<rootDir>/src/assets/$1"
...
}
}
2.3、报错:SyntaxError: Unexpected token 'export'
原因: moduleNameMapper配置错误;
解决方案: 修改路径映射规则。
"jest": {
"moduleNameMapper": {
"^@/(.*)$": "<rootDir>/src/$1" // 之前配的 "utils": "<rootDir>/src/utils"
"^Assets/(.*)$": "<rootDir>/src/assets/$1"
...
}
}
2.4、Jest 不支持 react组件写法导致的报错
原因:Jest 只是 Test Runner,只负责跑测试,tsc 负责转译 .ts 文件,webpack 则作为脚手架用于跑项目的工具,所以这三者本身不存在任何交集。
解决方案:
1、安装 @testing-library/react
2.5、Jest 不支持ant-design文件引入方式
原因: Jest 目前支持的是 cjs 规范,项目中用到了antd 和 ant-design,所以对于其使用的 rc-util 这种依赖,Jest 无法处理,需要手动转换一下;
解决方案:
"jest": {
"moduleNameMapper": {
"^@/(.*)$": "<rootDir>/src/$1"
"^Assets/(.*)$": "<rootDir>/src/assets/$1"
...
"antd": "<rootDir>/node_modules/antd/dist/antd.min.js", // antd 转换
"^@ant-design/(.*)$": "<rootDir>/node_modules/@ant-design/$1/dist/index.umd.min.js" // ant-design 转换
}
}
2.6、Jest不支持 styled-components
原因:Jest本身并没有直接支持styled-components或其他类似的CSS-in-JS库。这是因为Jest是一个JavaScript测试框架,主要专注于对函数和代码逻辑的测试,而不是对具体的CSS样式的测试。
解决思路: 既然单测重点在JavaScript 逻辑的测试,那么CSS忽略就好,那么怎么忽略呢?
网上查阅了一些资料,挨个试错,最终能解决问题的方案是:
1、在src目录中新建 styleMock.js文件,并写上代码,重置引入styled 文件时的导出对象;
module.exports = {};
2、配置Jest的 moduleNameMapper
"jest": {
"moduleNameMapper": {
"./styled": "<rootDir>/src/styleMock.js"
}
}
铺垫:到这里,这个问题看似解决了,为啥看似呢,因为这行已经不报错了。
我们继续总结下面的问题,一会还会说这个问题为什么没有解决。
2.7、@testing-library/react 内部代码报错
Tips: @testing-library/react 是在Jest 中 针对React组件的辅助工具库。
2.7.1、解决过程 and 思路(有一些冗长~有兴趣的可以点开看看)
这个问题困扰了我很久,我搜遍了国内外的大部分资料,都没有找到解决方案。
我甚至升级了 @testing-library/react 到 13版本,升级完,又报了其它的错误。后来查了官网才知道,
@testing-library/react 升级到13版本时,React至少需要升级到18。
React不能轻易升级到18,否则当前我们业务代码可能会出现很多兼容问题。排除升级这一个方案,我又陷入了苦恼。
我最后在Git hub上向原作者求助,github.com,想着也是一线希望吧。
没想到的是发布到github上,很快就有个叫MatanBobi 的国际友人回复了我,虽然他的回答没有立刻帮我解决当前的问题,但是他的一句话给了我一点点隐隐的提示(当时并没有意识到。)
因为我没有见过(0, _dom.configure){...} 这种方法调用写法,MatanBobi的回答是 :
到了晚上的时候,又有一个叫agentdylan的哥们回复了我的问题:
思考: MatanBobi说那句报错的代码是有转换器的,agentdylan说让我修改下 config的路径配置;
第二天一早来到公司 ,我看了下Jest的config配置,我首先按照agentdylan给的建议去修改了配置并重新运行测试命令,很遗憾没有解决。
结合上面两位国际友人的提示,我在想,那行代码有转换器,我肯定是什么地方把这个转换器给重置了呗。
过了一会,我突然很诧异为什么要给config配置路径,tsconfig里也没有 config 对应的配置,于是我删除了这个配置,重新运行测试命令。
喜大普奔!这个问题解决了。
解决一个大bug,本以为有了盼头,结果上面的 Jest不支持 styled-components的问题又出现了。
2.8、Jest不支持 styled-components,styled.div 包裹组件的方式报错
Tips: 下面截图中的Wrapper,完整代码:
import { Wrapper } from './styled';
所以说,2.6问题没有真正的解决,它只是暂时解决styled文件内的错误,而外部引用到组件使用这依然有问题。只不过代码逐行执行,中间又爆出其它的错误,让我误以为之前的问题解决了。
原因:上面我们新建的文件 styleMock.js 导出的是空对象,空对象获取 Wrapper,必然是undefined,而 <undefined></undefined>肯定出错。
2.6中说,我们重点关注JavaScript的运行逻辑,忽略CSS, 那么这里也就是要让 <Wrapper></Wrapper>不报错就行。
解决方案: styleMock.js 文件修改代码,将获取 Wrapper | RouteWrapper | HeaderWrapper 重置成 普通div即可。
const Wrapper = 'div';
const RouteWrapper = 'div';
const HeaderWrapper = 'div';
export { Wrapper, RouteWrapper, HeaderWrapper };
2.9、Jest 不支持 window,Dom Element等方法
原因:
Jest在默认情况下并不提供完整的浏览器环境,这是为了确保测试运行的快速和独立性。
Jest的设计初衷是作为一个用于测试JavaScript代码的工具,并专注于对纯粹的JavaScript逻辑进行测试。它提供了模拟函数、模拟模块、断言和其他相关工具,以便对代码的行为进行测试。但是,Jest并没有提供复杂的浏览器环境和完整的DOM支持
说白了,Jest只是运行在node环境下的测试库,你代码的Dom,TS,组件通通它都不认识。所以遇到Dom、TS、Reac组件等就需要相应的类库去辅助解决。
解决方案:在Src 目录的setupTests 文件下新增模拟代码:
export default global.matchMedia =
global.matchMedia ||
function (query) {
return {
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
};
};
三、总结
至此,解决了所有bug,虽然解 bug 是我们的目的,但是排查问题、寻找解决方案的过程是值得自己好好记录和总结的,对自身的技术提升和问题解决能力都有很大的帮助。
我们遇到过顽固bug经验的人,应该都体会过那种解决一个bug又出现一个新的bug,一个接着一个,遥遥无期的心情(😭)。
特别是翻阅网上所有能查到的资料都不能解决时,那种心态的崩溃。
既然不能求人,不能求资料,那还不得求自己(😊)。
我总结下来,解bug 是心态和行动的双重合作配合。
-
行动上
- 先分析问题,遇到自己知识储备之外的,上网查阅资料,多尝试;
- 当网上的资料都不能解决问题时,学会从资料中找细节和规律;拆解官方文档的描述,知道规则后自己灵活想出对应方案;
- 去除冗杂业务逻辑,模拟简单的运行环境,然后逐个添加业务逻辑,看是哪一步出的问题,再针对性的解决。
-
心态上
- 冷静、安静,戒躁;不要太着急;人往往在冷静分析的时候,就能找到解决之道;
- 常反思:有时候网上都搜不到类似的问题,那么是不是有可能自己使用的方式不对,或者出现干扰的代码(比如2.7@testing-library/react 内部代码报错的问题)。