React Jest单侧Bug解决方案--很全很详细

639 阅读8分钟

一、前言

经过前期对前端自动化测试的充分调研,决定采用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 规范,项目中用到了antdant-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的路径配置;

第二天一早来到公司 ,我看了下Jestconfig配置,我首先按照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环境下的测试库,你代码的DomTS,组件通通它都不认识。所以遇到DomTSReac组件等就需要相应的类库去辅助解决。

解决方案:在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 是心态和行动的双重合作配合。

  1. 行动上

    1. 先分析问题,遇到自己知识储备之外的,上网查阅资料,多尝试;
    2. 当网上的资料都不能解决问题时,学会从资料中找细节和规律;拆解官方文档的描述,知道规则后自己灵活想出对应方案;
    3. 去除冗杂业务逻辑,模拟简单的运行环境,然后逐个添加业务逻辑,看是哪一步出的问题,再针对性的解决。
  2. 心态上

    1. 冷静、安静,戒躁;不要太着急;人往往在冷静分析的时候,就能找到解决之道;
    2. 常反思:有时候网上都搜不到类似的问题,那么是不是有可能自己使用的方式不对,或者出现干扰的代码(比如2.7@testing-library/react 内部代码报错的问题)。