博客 / 詳情

返回

react + vite + testing-library單測環境構建

業務複雜多變迭代快速,加上編寫單測其實是耗費一定時間去做的,可能很多人認為編寫單元測試是一件吃力不討好的事兒,不會在項目中主動的去做單元測試,一兩年前筆者也是這樣的一種心態,對於單測嗤之以鼻,但是隨着看的書多了,學習的東西多了,明白了單測可有有效的保證我們一些核心功能的正確性,同樣可以反推我們的設計一些通用功能是否全面,再者也可以在我們改動一些功能後,校驗原有功能的正確性,説這麼多,還需要大家自己寫起來單測,一個東西好不好,只有用起來了才知道,在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進行單測用法簡短説明,喜歡的朋友點贊評論收藏三連一下,謝謝您們的支持~比心!!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.