likes
comments
collection
share

react + vite + testing-library单测环境构建

作者站长头像
站长
· 阅读数 15

业务复杂多变迭代快速,加上编写单测其实是耗费一定时间去做的,可能很多人认为编写单元测试是一件吃力不讨好的事儿,不会在项目中主动的去做单元测试,一两年前笔者也是这样的一种心态,对于单测嗤之以鼻,但是随着看的书多了,学习的东西多了,明白了单测可有有效的保证我们一些核心功能的正确性,同样可以反推我们的设计一些通用功能是否全面,再者也可以在我们改动一些功能后,校验原有功能的正确性,说这么多,还需要大家自己写起来单测,一个东西好不好,只有用起来了才知道,在vite下配置jest单测代码一上传至git,有兴趣的朋友,可以点此查阅;

(1)、测试项目准备

  如果我们通过creat-react-app创建项目会直接内置@testing-library/react,可以开箱即用,但是这里我们通过vite方式创建的react项目,vite构建的项目,默认是没有单测的,然后一步步完善test构建,这样做的好处呢就是我们自己熟悉配置构建流程,脱离cli脚手架工具,自由搭配;

  • 1、使用vite创建一个空白项目pnpm create vite react-test-example -- --template react-ts
  • 2、安装react单测相关依赖pnpm add @testing-library/react @testing-library/jest-dom jest -D
  • 3、pnpm jest --init生成jest配置文件

  下面我们就对于配置项目做个说明

  • 第一:我们先设置匹配那些文件作为test文件

    testMatch: [
      '<rootDir>/src/**/__tests__/**/*.{spec,test}.{js,jsx,ts,tsx}',
      '<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}',
      '<rootDir>/__test__/**/*.{spec,test}.{js,jsx,ts,tsx}',
    ],
  • 第二步:配置项目单测文件类型配置,注意这个配置内容是从左到右执行去检查匹配的,所以如果是ts主导的项目,我们就将ts类型文件放在最前面;

    moduleFileExtensions: [
      'ts', 'tsx', 'js', 'jsx'
     ]
  • 第三步:配置文件文件、路径解析办法,执行这个步骤之前,我们需要对于文件解析工具进行安装,具体如下:

    • 1、安装pnpm add identity-obj-proxy -D处理css文件
    • 2、对于jest单测文件,因为jest是运行在v8引擎中,也就是会以nodejs环境中来去执行,所以对于文件都要转义操作,常规有两种方式,第一种ts tsx方式写的单测,我们可以选中用ts-jest方式快速处理,第二种方式便是通过babel这种方式配置(繁琐一点),当然说道这儿了,我们得提一嘴今天我这里找到的另一种方式,那就是基于swc的方案,这套方案优势(我在实际项目亲测)就是执行jest单测文件效率会快过上面两种,速度快的不是一星半点,所以我推荐的用swc这套方式;
    • 3、基于第二步,我们安装swc单测相关工具内容pnpm add @swc/core @swc/jest -D
    moduleNameMapper: {
      '^.+\\.module\\.(css|sass|scss|less)$': 'identity-obj-proxy',
      '\\.svg$': 'identity-obj-proxy',
      // webpack  or  vite 等编译工具中涉及到的别名识别
    },
    transform: {
      '^.+\\.(js|jsx|ts|tsx)$': ["@swc/jest"],
    },
    transformIgnorePatterns: [
      '[/\\\\]node_modules/(?!(antd)/)[/\\\\].+\\.(js|jsx|ts|tsx)$',
    ],
  • 第四步:以上步骤针对于普通测试文件,如果我们需要对组件相关测试,我们就需要配置testEnvironment,现在我们项目上使用最新版本的jest后(jest 28 之后的版本),提示安装的是jest-environment-jsdom,我们安装后,按照下面配置;

    testEnvironment: 'jsdom',
  • 第五步:上面步骤我们完成基本版本的jest配置,具体关于coverage等等这些配置可以查看官网,还有setupFiles相关配置根据具体使用情况增加,后续我们使用@testing-library/react进行,组件测试都需要依赖@testing-library/js-dom所以我们就直接设置setupFilesAfterEnv,将每个单测都需要的公共内容统一添加,具体如下:

    // ./jest/setupJestDom.ts
    import '@testing-library/jest-dom'
    
    // 调整jest.config.js, 增加以下内容
     setupFilesAfterEnv: [
      '<rootDir>/jest/setupJestDom.ts'
     ],
  • 第六步: 每次执行完毕后,不自动清理单测缓存,这样执行效率会快

    setupFiles: [
      '@testing-library/react/dont-cleanup-after-each'
    ],

(2)、@testing-library/react的基本用法

  上面我们做了jest项目的配置,基本上可以满足我们写jest单测了,下面我们就用使用@testing-library/react方式做一下单元测试的写法,这里我先将@testing-library/reactapi文档地址放在这儿,方便参考使用;  首先我们对于常规组件测试写法,具体如下:

import React from 'react'

const HelloWorld: React.FC = () => {
  return <div>hello world</div>
}

export default HelloWorld
单元测试写法
import React from 'react'
import { render, screen } from '@testing-library/react'
import HelloWorld from '../src/component/HelloWorld'

describe('HelloWorld Component', () => {
  it('render', () => {
    render(<HelloWorld></HelloWorld>)
    // 组件渲染结果打印出来,方便我们去对照
    screen.debug()
    expect(screen.getByText('hello world')).toBeInTheDocument()
  })
})

我们这里总结一下基本用法:

  • render:渲染react组件
  • screen:testing-library/react 提供的全局dom对象,我们可以通过screen去查dom结构
  • 查找元素:findBy...、getBy...、queryBy

    • 对于getBy... 与 queryBy...两个用法类似,都是去查找页面内容,返回的是fiberNode;
    • 对于findBy...返回的是一个promise,如果页面初始状态没有的元素结构,最终会渲染出来的结构,可以通过这种方式来处理,例如我们异步请求数据,渲染页面结构,我们就可以通过findBy方法实现,如下所示:
    import React from 'react'
    import { render, screen } from '@testing-library/react'
    import HelloWorld from '../src/component/HelloWorld'
    
    describe('HelloWorld Component', () => {
    it('render', async () => {
      render(<HelloWorld></HelloWorld>)
      // 是在写单测不清楚渲染出来的是啥,可以进行debug
      screen.debug()
      expect(await screen.findByText('hello world')).toBeInTheDocument()
    })
    })
  • 针对jest-dom扩展的匹配器(上面用到的toBeInTheDocument()),还记得我们通过setupFiles插入进去的工具库@testing-library/jest-dom,里面提供了一系列匹配器,方便我们去做test,建议去看官方例子,没必要死记硬背,实际工作要用了就查阅文档;
  • fireEvent,进行事件模拟单元测试,具体用法如下:

    import React, { useState } from 'react'
    
    interface HelloWorldProps {
    onClick: () => void
    }
    
    const HelloWorld: React.FC<HelloWorldProps> = ({ onClick }) => {
    return <div>
      <div>hello world</div>
      <button onClick={onClick}>增加</button>
    </div>
    }
    
    export default HelloWorld

      单测文件写法如下:

    import React from 'react'
    import { render, screen, fireEvent } from '@testing-library/react'
    import HelloWorld from '../src/component/HelloWorld'
    
    describe('HelloWorld Component', () => {
    it('render', async () => {
      const onClick = jest.fn()
      render(<HelloWorld onClick={onClick}></HelloWorld>)
      // 是在写单测不清楚渲染出来的是啥,可以进行debug
      screen.debug()
      expect(await screen.findByText('hello world')).toBeInTheDocument()
      // 模拟事件
      fireEvent.click(screen.getByText(/增加/i))
      expect(onClick).toHaveBeenCalledTimes(1)
    })
    })

    (3)、@testing-library/react-hooks 对于hooks单元测试办法

      针对于hooks的单元测试,我们可以安装@testing-library/react-hooks工具进行hooks单测,具体安装如下:

  • 首先,我们需要安装pnpm add @testing-library/react-hooks -D
  • 其次,我们需要注意@testing-library/react-hooks 不与react版本捆绑在一起,所以如果我们需要安装react-test-renderer 或者 react-dom两个其中一个,二者都有,会默认以react-test-renderer为第一优先级;

下面对于hooks单测如下:

import { useCallback, useState } from "react"


function useCounter() {
  const [count, setCount] = useState(0)

  const increment = useCallback(() => setCount((x) => x + 1), [])

  return { count, increment }
}

export default useCounter
import { renderHook, act } from '@testing-library/react-hooks'
import useCounter from '../src/useCounter'

test('Counter', () => {
  const { result } = renderHook(() => useCounter())

  act(() => {
    result.current.increment()
  })

  expect(result.current.count).toBe(1)
})

  单元测试内容分很多,我这里主要就是项目构建,使用testing-library进行单测用法简短说明,喜欢的朋友点赞评论收藏三连一下,谢谢您们的支持~比心!!