日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Jest + React Testing Library 单测总结

發布時間:2023/12/9 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Jest + React Testing Library 单测总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

大家好,我是若川。持續組織了6個月源碼共讀活動,感興趣的可以點此加我微信 ruochuan02?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列

1、背景

以前還是學生的時候,有學習一門與測試相關的課程。那個時候,覺得測試就是寫 test case,寫斷言,跑測試,以及查看 test case 的 coverage。整個流程和寫法也不是特別難,所以就理所當然地覺得,寫測試也不是特別難。

加上之前實際的工作中,也沒有太多的寫測試的經歷,所以當自己需要對組件庫補充單元測試的時候,發現并不能照葫蘆畫瓢來寫單測。一時不知道該如何下手,也不知道如何編寫有效的單測,人有點懵,于是就比較粗略地研究了一下前端組件單測。

1.1 單測的目的

  • 在頻繁的需求變動中可控地保障代碼變動的影響范圍

  • 提升代碼質量和開發測試效率

  • 保證代碼的整潔清晰

  • ......

總之單測是一個保證產品質量的非常強大的手段。

1.2 測試框架和 UI 組件測試工具

而說起前端的測試框架和工具,比較主流的 JavaScript 測試框架有 Jest、Jasmine、Mocha 等等,并且還有一些 UI 組件測試工具,比如 testing-libraray,enzyme 等等。

測試框架和 UI 組件測試工具之間并不是相互依賴、非此即彼的,而是可以根據不同工具的性質做不同的搭配。目前騰訊課堂基于 Tdesign 開發的素材庫組件的單測,就是使用 Jest + React Testing Library 來完成。

1.3 組件單測須知

在開始進行組件單測的時候,有幾個因素我們需要考慮:

  • 組件是否按照既定的條件 / 邏輯進行渲染

  • 組件的事件回調是否正確

  • 異步接口如何校驗

  • 異步執行完畢后的操作如何校驗

  • ......

當然不止這些列舉出來的,根據不同的業務場景,我們考慮的因素需要更全面更細致。

2、Jest 的使用

Jest 的安裝這里就不贅述了,如果使用 create-react-app 來創建項目,Jest 和 React Testing Library(RTL) 都已經默認安裝了。

如果想要看如何安裝 Jest,可以參考:Jest 上手。

Jest 常用的配置項在根目錄中的 jest.config.js 中,常用的配置可以參考:Jest 配置文件。

2.1 Jest 基礎 API

Jest 的最基礎,最常用的三個 API 是:describe、test 和 expect。

  • describe 是 test suite(測試套件)

  • test (也可以寫成 it) 是 test case(測試用例)

  • expect 是斷言

import aFunction from'./function.js';// 假設 aFunction 讀取一個 bool 參數,并返回該 bool 參數 describe('a example test suite', () => {test('function return true', () => {expect(aFunction(true)).toBe(true);// 測試通過});test('function return false', () => {expect(aFunction(false)).toBe(false);// 測試通過}); });

通過運行?npm run jest?(運行所有的 test suite 和 test case,以及斷言),或者?npm run jest -t somefile.test.tsx(運行指定文件中的測試用例),就可以得到測試結果,如:

當然,如果想要看到覆蓋率的報告,可以使用?jest --coverage,或者?jest-report。

在 VS Code 中,我們也可以安裝插件:Jest Runner。

在代碼中,就可以快速跑測試用例,可以說非常的方便了。

如果在使用 Jest runner 的時候出現 Node.js 相關的報錯,可以查看一下當前 Node.js 的使用版本,切換到 14.17.0 版本即可。

2.2 Jest 匹配器

Jest 匹配器是在 expect 斷言時,用來檢查值是否滿足一定的條件。例如上面的例子中:

expect(aFunction(true)).toBe(true)

其中 toBe () 就是用來比較 aFunction (true) 的值是否為 true。

完整的 Jest 匹配器可以在?這里?查看,下面也列舉一些常用的匹配器:

匹配器說明
.toBe(value)相等性,檢查規則為 === + Object.is
.toEqual(value)相等性,遞歸對比對象字段
.toBeInstanceOf(Class)檢查是否屬于某一個 Class 的 instance
.toHaveProperty(keyPath, value)檢查斷言中的對象是否包含 keyPath 字段,或可以檢查該對象的值是否等于 value
.toBeGreaterThan(number)大于 number
.toBeGreaterThanOrEqual(number)大于等于 number
.toBeNaN()值是否是 NaN
.toMatch(regexp or String)字符串的相等性,可以填入 string 或者一個正則
.toContain(item)substring
.toHaveLength(number)字符串長度

其實在 Testing Library 庫中,還提供了一些匹配器專門用來測試前端組件,這些擴展的匹配器會讓前端組件的測試變得更靈活。除了前端組件的匹配器,一些擴展庫也依據不同的測試場景衍生出了很多其他的匹配器。

2.3 Jest Mock

在查看官方文檔的時候,Jest 匹配器中還有一類匹配器專門用來檢查 Jest Mock 函數的。在組件單測中,有的時候我們可能只關注一個函數是否被正確地調用了,或者只想要某個函數的返回值來支持該組件渲染邏輯是否正確,而并不關心這個函數本身的邏輯。正如官方文檔中強調的那樣:

Testing Library encourages you to avoid testing implementation details like the internals of a component you're testing.

測試庫鼓勵您避免測試實現細節,例如您正在測試的組件的內部結構。

所以,Jest Mock 的意義就在于可以幫助我們完成下面這些事情:

  • 有些模塊可能在測試環境中不能很好地工作,或者對測試本身不是很重要,使用虛擬數據來 mock 這些模塊,可以使你為代碼編寫測試變得更容易;

  • 如果不想在測試中加載這個組件,我們可以將依賴 mock 到一個虛擬組件;

  • 測試組件處于不同狀態下的表現;

  • mock 一些子組件,可以幫助減小快照的大小,并使它們在代碼評審中保持可讀性;

  • ......

  • Jest Mock 的常用 API 是:jest.fn () 和 jest.mock ()。

    2.3.1 jest.fn()

    通過 jest.fn(implementation) 可以創建 mock 函數。如果沒有定義函數內部的實現,mock 函數會返回 undefined。

    // 定義一個 mock 的函數,因為沒有函數體,所以 mockFn 會 return undefined const mockFn = jest.fn();// mockFn 調用 mockFn(); // 雖然沒有定義函數體,但是 mockFn 被調用過了 expect(mockFn).toHaveBeenCalled();const res = mockFn('a','b','c');// 斷言 mockFn 的執行后返回 undefined expect(res).toBeUndefined();// 斷言mockFn被調用了兩次 expect(mockFn).toBeCalledTimes(2);// 斷言mockFn傳入的參數為a,b,c expect(mockFn).toHaveBeenCalledWith('a','b','c');// 定義implementation,自定義函數體: const returnsTrue = jest.fn(() =>true); // 定義了函數體 console.log(returnsTrue()); // true// 可以給mock的函數設置返回值 const returnSomething = jest.fn().mockReturnValue('hello world'); expect(returnSomething()).toBe('hello world');// mock也可以返回一個Promise const promiseFn = jest.fn().mockResolvedValue('hello promise'); const promiseRes = await promiseFn(); expect(promiseRes).toBe('hello promise');

    2.3.2 jest.mock(moduleName, factory, options)

    jest.mock() 可以幫助我們去 mock 一些 ajax 請求,作為前端只需要去確認這個異步請求發送成功就好了,至于后端接口返回什么內容我們就不關注了,這是后端自動化測試要做的事情。

    // users.js 獲取所有user信息 import axios from'axios';class Users {staticall() {return axios.get('.../users.json').then(resp => resp.data);} }exportdefault Users;// user.test.js import axios from'axios'; import Users from'./users';jest.mock('axios');test('should fetch users', () => {const users = [{name: 'Bob'}];const resp = {data: users};axios.get.mockResolvedValue(resp);// or you could use the following depending on your use case:// axios.get.mockImplementation(() => Promise.resolve(resp))return Users.all().then(data => expect(data).toEqual(users)); });

    2.3.3 Jest Mock 的匹配器

    Jest 匹配器中還有一類匹配器專門用來檢查 jest mock() 的,比如:

    • 名字

      • mockFn.mockName(value)

      • mockFn.getMockName()

    • 運行情況

      • mockFn.mock.calls:傳的參數

      • mockFn.mock.results:得到的返回值

      • mockFn.mock.instances:mock 包裝器實例

    • 模擬函數

      • mockFn.mockImplementation(fn):重新聲明被 mock 的函數

      • mockFn.mockImplementationOnce(fn)

    • 模擬結果

      • mockFn.mockReturnThis()

      • mockFn.mockReturnValue(value)

      • mockFn.mockReturnValueOnce(value)

      • mockFn.mockResolvedValue(value)

      • mockFn.mockResolvedValueOnce(value)

      • mockFn.mockRejectedValue(value)

      • mockFn.mockRejectedValueOnce(value)

    2.4 Jest 的擴展閱讀材料

    • Jest 學習指南

    • 那些年錯過的 React 組件單元測試

    • 使用 Jest 測試 JavaScript (Mock 篇)

    3、React Testing Library

    testing library?是一個測試 React 組件的測試庫,它的核心理念就是:

    The more your tests resemble the way your software is used, the more confidence they can give you.

    測試越類似于軟件使用方式,就越能給測試信心。

    3.1 render & debug

    在測試用例中渲染內容,可以使用 RTL 庫中的 render,render 函數可以為我們在測試用例中渲染 React 組件。

    被渲染的組件,可以通過 debug 函數或者 screen 的 debug 函數在控制臺輸出組件的 HTML 結構。例如下面的 Dropdown 組件的例子:

    import { render, screen } from '@testing-library/react'; import Dropdown from '../index'; // 要測試的組件describe('dropdown test', () => {it('render Dropdown', () => {// 渲染 Dropdown 組件const comp = render(<Dropdown />);comp.debug();screen.debug();// 這兩種都可以打印出來渲染組件的結構}); });

    其實,在我們編寫組件測試用例時,都可以通過 debug 函數把組件渲染結果打印出來,這可以提高我們編寫用例時的效率,同時,這一特點也很符合 RTL 的設計觀念。

    3.2 screen

    在上面的例子中,其實我們也使用到了庫中的 screen。screen 為測試用例提供了一個全局 DOM 環境,通過這個環境,我們就可以去使用庫中提供的不同函數去定位元素,定位后的元素可以用于斷言判斷或者用戶交互。

    3.3 定位元素

    3.3.1 Query 類型

    定位元素的方法在 RTL 中稱為 Query,Query 幫助我們去找到頁面上的元素。RTL 提供了三種 Query 的類型:"get", "find", "query"。

    Query 類型未找到元素找到 1 個元素找到多個元素Retry (Async/Await)
    Single Element



    getBy...Throw errorReturn elementThrow errorNo
    queryBy...Return nullReturn elementThrow errorNo
    findBy...Throw errorReturn elementThrow errorYes
    Multiple Elements



    getAllBy...Throw errorReturn arrayReturn arrayNo
    queryAllBy...Return []Return arrayReturn arrayNo
    findAllBy...Throw errorReturn arrayReturn arrayYes

    從上面的表格可以看出來,定位的方法在找單個元素時和多個元素時會做了一些區別,比如 getBy... 如果找到了多個元素就會 throw error,這時就需要使用 getAllBy...。

    get 和 query 的區別主要是在未找到元素時,queryBy 會返回 null,這對于我們測試一個元素是否存在時非常有幫助。

    而 findby 的作用主要用于那些最終會顯示在頁面當中的異步元素。

    3.3.2 Query 內容

    那么,getBy...、queryBy... 和 findBy... 后面具體可以查詢什么內容呢?

    • 主要

      • ByLabelText:用于表單的 label

      • ByPlaceholderText:用于表單

      • ByText:查詢 TextNode

      • ByDisplayValue:輸入框等當前值

    • 語義

      • ByAltText:img 的 alt 屬性

      • ByTitle:title 屬性或元素

      • ByRole:ARIA role,可以定位到輔助樹中的元素

    • Id

      • getByTestId:函數需要在源代碼中添加 data-testid 屬性才能使用

    一般而言,getByText 和 getByRole 應該是元素的首選定位類型。

    import { render, screen } from'@testing-library/react'; import Dropdown from'../index'; // 要測試的組件const propsRender = {commonStyle: {},data: {btnTheme: 'default',btnVariant: 'text',btnText: 'test', // 給 dropdown 的 button 設置文字 'test'trigger: 'click',},style: {},meta: {previewMode: true,isEditor: false},on: jest.fn(),off: jest.fn(),emit: jest.fn(), };describe('dropdown test', () => {it('render Dropdown', () => {// 渲染 Dropdown 組件const comp = render(<Dropdown />);// 使用 queryByText("test") 定位這個 button 的文字內容,然后使用斷言+匹配做測試expect(screen.queryByText("test")).toBeInTheDocument();}); });

    findBy 的使用方法

    假如在 Component 組件中定義一行文字 “hello world” 和一個定時器,在組件渲染 3 秒后再顯示這行字。

    describe('test hello world', () => {test('renders component', async () => {render(<Component />);// 在組件的初始化渲染中,我們在 HTML 中無法通過 queryBy 找到 “hello world”,因為它三秒后才能出現expect(screen.queryByText(/hello world/)).toBeNull();// await 一個新的元素被找到,并且最終確實被找到當 promise resolves 并且組件重新渲染之后。expect(await screen.findByText(/hello world/)).toBeInTheDocument();}); });

    對于任何開始不顯示、但遲早會顯示的元素,要使用 findBy。如果你想要驗證一個元素不在頁面中,使用 queryBy,否則默認使用 getBy。

    RTL 所有定位方法可?點擊?查看。

    3.4 RTL +?Jest 匹配器

    在?2.2 Jest 匹配器?中可以看到 Jest 提供了一些匹配器,然而 Jest 自己提供的匹配器很難去實現組件測試的一些特殊條件,所以 RTL 自己實現了一個 Jest 匹配器的擴展包:jest-dom。

    • Custom matchers

      • toBeDisabled

      • toBeEnabled

      • toBeEmptyDOMElement

      • toBeInTheDocument

      • toBeInvalid

      • toBeRequired

      • toBeValid

      • toBeVisible

      • toContainElement

      • toContainHTML

      • toHaveAccessibleDescription

      • toHaveAccessibleName

      • toHaveAttribute

      • toHaveClass

      • toHaveFocus

      • toHaveFormValues

      • toHaveStyle

      • toHaveTextContent

      • toHaveValue

      • toHaveDisplayValue

      • toBeChecked

      • toBePartiallyChecked

      • toHaveErrorMessage

    • Deprecated matchers

      • toBeEmpty

      • toBeInTheDOM

      • toHaveDescription

    3.5 事件:FireEvent

    實際的用戶交互可以通過 RTL 的 fireEvent 函數去模擬。

    fireEvent(node: HTMLElement, event: Event) fireEvent[eventName](node: HTMLElement, eventProperties: Object)// <button>Submit</button> fireEvent(getByText(container, 'Submit'),new MouseEvent('click', {bubbles: true,cancelable: true,}), );// 兩種寫法 fireEvent(element, new MouseEvent('click', options?)); fireEvent.click(element, options?);

    fireEvent 函數需要兩個參數,一個參數是定位的元素 node,另一個參數是 event。這個例子中就模擬了用戶點擊了 button,同時 fireEvent 有兩種寫法。

    事件 options 描述

    屬性 / 方法描述
    bubbles返回特定事件是否為冒泡事件。
    cancelBubble設置或返回事件是否應該向上層級進行傳播。
    cancelable返回事件是否可以阻止其默認操作。
    composed指示該事件是否可以從 Shadow DOM 傳遞到一般的 DOM。
    composedPath()返回事件的路徑。
    createEvent()創建新事件。
    currentTarget返回其事件偵聽器觸發事件的元素。
    defaultPrevented返回是否為事件調用 preventDefault () 方法。
    eventPhase返回當前正在評估事件流處于哪個階段。
    isTrusted返回事件是否受信任。
    target返回觸發事件的元素。
    timeStamp返回創建事件的時間(相對于紀元的毫秒數)。
    type返回事件名稱。

    常用 fireEvent:

    鍵盤:

    • keyDown

    • keyPress

    • keyUp

    聚焦:

    • focus

    • blur

    表單:

    • change

    • input

    • invalid

    • submit

    • reset

    鼠標:

    • click

    • dblClick

    • drag

    fireEvent API 列表可?點擊?查看。

    4、寫在最后

    測試在整個需求開發的流程中起著重要作用,它對于需求產品的質量提供了強而有力的保障。但是在實際的工作中,產品的迭代、需求的變更以及各種不確定的因素,我們經常會陷入“bug的輪回” —— 關上一個bug,點亮另一個bug。

    隨著業務復雜度的提升,測試的人力成本也會越來越高。面對這些痛點,作為“懶而聰明”的前端開發,我也常常在思考有什么方法可以在解放雙(ren)手(li)的同時,又能保證產品的質量,也不必在每次需求上線時緊張兮兮地盯著告警看板,生怕發的版本影響了其他的功能。所以,我相信借助于測試的力量,這些痛點終有一天會逐個擊破。

    就像開頭提到的,本文只是“比較粗略”地瀏覽了 Jest + RTL,相較于整個前端單測來說只是冰山一角。希望在日后工作的每一天能不斷地探索這個領域,也希望在不久的將來,我也能 “快樂編碼,自信發布”。

    ·················?若川簡介?·················

    你好,我是若川,畢業于江西高校?,F在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
    從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
    同時,最近組織了源碼共讀活動,幫助3000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。

    識別方二維碼加我微信、拉你進源碼共讀

    今日話題

    略。分享、收藏、點贊、在看我的文章就是對我最大的支持~

    總結

    以上是生活随笔為你收集整理的Jest + React Testing Library 单测总结的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。