关闭

Jest + React 单元测试最佳实践(下)

发表于:2023-5-15 09:45

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:袋鼠云数栈UED团队    来源:稀土掘金

  在项目中遇到的一些问题
  1、执行 pnpm test 报错
  原因:当引入外部库是es模块时,?jest无法处理导致报错,可以通过 babel-jest 进行处理,根据官方文档:jestjs.io/zh-Hans/doc…,还有一种就是修改jest.config.js 加入preset: 'ts-jest' ,会让部分测试成功但是还是会存在一些问题。
  方案一:采用了 babel-jest 进行处理
  pnpm add -D babel-jest @babel/core @babel/preset-env
  安装完以后在工程的根目录下创建一个babel.config.js
  module.exports = {
    presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
  };
  修改jest.config.js,增加transform
  transform: {
    "^.+\\.js$": "babel-jest",
    "^.+\\.(ts|tsx)$": "ts-jest",
  },
  方案二:仍然采用 ts-jest ,把引起报错文件的后缀,如 js 改为 ts 即可
  2、ts-jest和jest版本未对应
  报如下错误:
  升级后版本(仅供参考)
  3、toBeInTheDocument、toHaveClass等报错
  类型检查错误,应该是@testing-library/jest-dom类型没被引入导致的
  有以下两种方案,都需要修改tsconfig.json
  // 方案一,删除typeRoots
  "typeRoots": ["node", "node_modules/@types", "./typings"]
  // 方案二,添加types
  "types": ["@testing-library/jest-dom"]
  4、Cannot find namespace 'NodeJS’
  修改 tsconfig.json ,往 types 中加入 node
  "types": ["node", "@testing-library/jest-dom"]
  5、module 'tslib' cannot be found
  报错信息如下:
  原因是在 tsconfig.json 中开启了如下配置:
  "importHelpers": true,
  解决方案如下:
  方案一:
  "importHelpers": false,
  方案二:
  pnpm add tslib
  并且修改 tsconfig
  "paths": {
    "tslib" : ["./node_modules/tslib/tslib.d.ts"] //在paths下添加tslib路径
  }
  6、由于单测的运行环境问题,当遇到某些方法没有的时候尝试mock下
  例如:
  解决方案如下:
  (global as any).document.createRange = () => ({
    selectNodeContents: jest.fn(),
    getBoundingClientRect: jest.fn(() => ({
      width: 500,
    })),
  });
  7、多个单测文件缺失某一个方法,可以采用如下配置
  例如:多个单测文件有如下报错:
  那么首先在 jest.comfig.js 中添加配置:
  module.exports = {
    setupFilesAfterEnv: ['./setupTests.ts'],
    // ...
  }
  然后在 setupTests.ts 文件中:
  Object.defineProperty(window, 'matchMedia', {
    writable: true,
    value: jest.fn().mockImplementation((query) => ({
      matches: false,
      media: query,
      onchange: null,
      addListener: jest.fn(), // deprecated
      removeListener: jest.fn(), // deprecated
      addEventListener: jest.fn(),
      removeEventListener: jest.fn(),
      dispatchEvent: jest.fn(),
    })),
  });
  8、The error below may be caused by using the wrong test environment;Consider using the "jsdom" test environment
  依赖版本:
  "ts-jest": "^28.0.8",
  "jest": "^28.1.2",
  解决方法: 在 jest.config.js 中添加配置
  module.exports = {
    verbose: true,
    testEnvironment: 'jsdom',
    // ...
  }
  并安装 jest-environment-jsdom (注意: 仅 jest 28 及更高版本需要安装此依赖项)
  {
    "devDependencies": {
      "jest-environment-jsdom": "^28.1.2",
    }
  }
  9、Echarts 单元测试 canvas 报错
  在写 Echarts 单元测试的时候,会有 canvas 报错。原因很明显,Echarts 依赖了 canvas。
  解决办法:使用 jest-canvas-mock,参考:Error: Not implemented: HTMLCanvasElement.prototype.getContext
  注意:直接引入 canvas 虽然可以解决单元测试的报错,但是会导致安装依赖会有偶发性 canvas 报错。
  10、引入了第三方的组件CodeMirrorEditor写单测报错
  在对该组件进行单测时,由于引入了第三方的组件 CodeMirrorEditor ,编译时出现了以下问题,原因是试图导入 jest 无法解析的文件,而从实际上来说我们对当前组件的测试其实并不用去编译 dt-react-codemirror-editor。
  因此,在 jest.config.js 文件加入编译时需要忽略的文件。
  再次运行测试,然而。。。。。。
  好吧,又失败了进入 index 查看,提示找不到 style 文件但是文件夹里又是存在的,初步尝试是否由于文件扩展名起,保存测试通过,但是修改 node_modules 里的文件扩展名无法从根本解决该问题,按照推荐提示在测试覆盖文件扩展名 moduleFileExtensions 内加入 css。
  再次尝试,然而。。。。。。jest 去编译了 style.css 文件,然后它无法解析失败了,查看配置。
  发现已经配置了当匹配到 css 文件时映射到一个空对象里,并不会去编译原样式文件,原因是由于加入到了编译覆盖的文件扩展名数组里 moduleFileExtensions,因此无法采用推荐方法。
  再次回顾问题产生的原因,jest 无法找到 style 文件但是找到了 style.css 文件,但是 style 文件我们并不需要进行编译,加入 moduleNameMapper 当找到 style 文件时映射到一个空对象的文件里。
  11、Route && Link
  在测试面包屑组件BreadCrumb时,因为面包屑组件中只用了 Link 标签,最终会被转成 a 标签,用来路由导航。如下写法是将 Link 和 route 放在一个组件之中。然后报错:Invariant Violation: <Link>s rendered outside of a router context cannot navigate。
  import React from 'react'
  import BreadCrumb from '../index';
  import { render, fireEvent } from '@testing-library/react'
  import '@testing-library/jest-dom/extend-expect';
  import { Router, Switch, Route } from 'react-router-dom';
  import { createMemoryHistory } from 'history'
  const testProps = {
    breadcrumbNameMap: [
      {
         name: 'home',
         path: '/home'
      },
      {
         name: 'home/about',
         path: '/home/about'
      }
    ],
    style: {
      backgroundColor: '#dedede'
    }
  }
  const Home = () => <h1>home</h1>
  const About = () => <h1>about</h1>
  const App = () => {
    const history = createMemoryHistory();
    return (
       <>
         <Router history={history}>
           {< BreadCrumb {...testProps} />}
            <Switch>
               <Route exact path="/main" component={Home} />
               <Route path="/main/home" component={About} />
            </Switch>
          </Router>
        </>
      )
  }
  describe('test breadcrumb', () => {
    test('should navigate to home when click ', () => {
      const { container, getByTestId } = render(<App />);
      expect(container.innerHTML).toMatch('about')
      fireEvent.click(getByTestId('/home-link'))
        expect(container.innerHTML).toMatch('home')
    })
  })
  主要原因是版本原因:3.0版本路由不支持这种写法。3.0是将react-router 和react-router-dom分开的;而4.0路由将其合并成了一个包,在具体使用时应该基于不同的平台要使用不同的绑定库。例如在浏览器中使用 react router,就安装 react-router-dom 库;在 React Native 中使用 React router 就应该安装 react-router-native 库,但是我们不会安装 react-router了。项目中用的是3.0版本路由,于是改为3.0写法,将link和router分开写在两个组件中,通过测试。
  const testProps = {
    breadcrumbNameMap: [
      {
        name: 'home',
        path: '/home'
      },
      {
        name: 'about',
        path: '/about'
      }
    ],
    style: {
      backgroundColor: '#dedede'
    }
  }
  const App = (props) => {
    return (
      <div>
        {<BreadCrumb {...testProps} />}
        {props.children}
      </div>
    )
  }
  const About = () => <h1>about page</h1>
  const Home = () => <h1>home</h1>
  describe('test breadcrumb', () => {
    afterEach(() => {
      cleanup();
    })
    test('should navigate to home router when click ', () => {
      const history = createMemoryHistory()
      const { container, getByTestId } = render(
      <Router history={history}>
        <Route path="/" component={App}>
          <IndexRoute component={About} />
          <Route path="/about" component={About} />
          <Route path="/home" component={Home} />
        </Route>
      </Router>
    );
      expect(container.innerHTML).toMatch('about')
      fireEvent.click(getByTestId('/home-link'))
      expect(container.innerHTML).toMatch('home')
    })
  })
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号