詳解如何使用Jest測試React組件
值得注意的是,Jest 并不是專門針對 React:您可以使用它來測試任何 JavaScript 應(yīng)用程序。然而,它提供的一些功能對于測試用戶界面來說非常方便,這就是為什么它非常適合 React。
應(yīng)用測試示例
在我們測試任何東西之前,我們需要一個應(yīng)用程序來測試!您可以在GitHub上找到它以及我們即將編寫的所有測試。如果您想使用該應(yīng)用程序來感受一下,您還可以在線找到現(xiàn)場演示。
該應(yīng)用程序使用 ES2015 編寫的,使用帶有 Babel ES2015 和 React 預(yù)設(shè)的 webpack 進行編譯。我不會詳細介紹構(gòu)建設(shè)置,但如果您想查看的話,一切都在GitHub存儲庫中。您可以在自述文件中找到有關(guān)如何在本地運行應(yīng)用程序的完整說明。
應(yīng)用程序的入口是app/index.js
:
render( <Todos />, document.getElementById('app') );
該Todos
組件是應(yīng)用程序的主要內(nèi)容。它包含所有狀態(tài)(該應(yīng)用程序的硬編碼數(shù)據(jù),實際上可能來自 API 或類似數(shù)據(jù)),并有渲染兩個子組件。
因為Todos
組件包含所有狀態(tài),所以每當(dāng)有任何變化時,它需要Todo
和AddTodo
組件通知它。因此,它將函數(shù)向下傳遞到這些組件中,當(dāng)某些數(shù)據(jù)發(fā)生變化時它們可以調(diào)用這些函數(shù),并Todos
可以相應(yīng)地更新狀態(tài)。
最后,現(xiàn)在您會注意到所有業(yè)務(wù)邏輯都包含在app/state-functions.js
:
export function toggleDone(todos, id) {…} export function addTodo(todos, todo) {…} export function deleteTodo(todos, id) {…}
如果您熟悉 Redux,它們與 Redux 所謂的reducer
非常相似。事實上,如果這個應(yīng)用程序變得更大,會考慮遷移到 Redux 以獲得更明確、結(jié)構(gòu)化的數(shù)據(jù)方法。
選擇 TDD 而不是 TDD?
關(guān)于測試驅(qū)動開發(fā)的優(yōu)缺點已經(jīng)有很多文章寫過了,開發(fā)人員需要先編寫測試,然后再編寫修復(fù)測試的代碼。這背后的想法是,通過先編寫測試,您必須思考您正在編寫的API,并且它可能會導(dǎo)致更好的設(shè)計。我認為這在很大程度上取決于個人偏好和測試的類型。我發(fā)現(xiàn),對于React組件,我喜歡先編寫組件,然后為最重要的功能添加測試。然而,如果您發(fā)現(xiàn)先為組件編寫測試符合您的工作流程,那么您應(yīng)該這樣做。在這里沒有硬性規(guī)定,做適合您和您的團隊感覺最好的事情。
什么是Jest
Jest首次發(fā)布于2014年,盡管它最初引起了很多關(guān)注,但該項目一度停滯不前,并沒有得到很積極的開發(fā)。然而,F(xiàn)acebook投入了大量精力來改進Jest,并發(fā)布了一些令人印象深刻的變化,值得重新考慮。與最初的開源發(fā)布相比,Jest僅保留了名稱和標(biāo)志。其他一切都已經(jīng)改變和重寫。如果您想了解更多信息,可以閱讀Christoph Pojer的評論,他在其中討論了該項目的當(dāng)前狀態(tài)。
如果您對使用其他框架設(shè)置Babel、React和JSX測試感到沮喪,那么我強烈建議您嘗試Jest。如果您發(fā)現(xiàn)現(xiàn)有的測試安裝速度很慢,我也強烈推薦Jest。它可以自動并行運行測試,其watch模式能夠運行與更改文件相關(guān)的測試,這在您擁有大量測試套件時是非常寶貴的。它已經(jīng)配置了JSDom,這意味著您可以編寫瀏覽器測試但通過Node運行它們。它可以處理異步測試,并且具有內(nèi)置的高級功能,如mocking、spy和stub。
安裝和配置 Jest
首先,我們需要安裝 Jest。因為我們也在使用 Babel,所以我們將安裝另外幾個模塊,使 Jest 和 Babel 可以正常使用:
npm install --save-dev jest babel-jest @babel/core @babel/preset-env @babel/preset-react
您還需要一個babel.config.js
包含 Babel 配置的文件,以使用您需要的任何預(yù)設(shè)和插件。如下所示:
module.exports = { presets: [ '@babel/preset-env', '@babel/preset-react', ], };
本文不會深入探討 Babel 的設(shè)置。如果您想具體了解有關(guān) Babel 的更多信息,我推薦Babel 使用指南。
我們還不會安裝任何 React 測試工具,因為我們不會從測試組件開始,而是從測試狀態(tài)函數(shù)開始。
Jest 期望在一個文件夾中找到我們的測試__tests__
,這已成為 JavaScript 社區(qū)中的一種流行慣例,我們將在這里堅持這一慣例。如果您不喜歡該__tests__
設(shè)置,開箱即用的 Jest 還支持查找任何.test.js
文件.spec.js
。
由于我們將測試我們的狀態(tài)函數(shù),因此請繼續(xù)創(chuàng)建__tests__/state-functions.test.js
。 下面是一個測試用例,進行測試檢查Jest配置是否正常。
describe('Addition', () => { it('knows that 2 and 2 make 4', () => { expect(2 + 2).toBe(4); }); });
現(xiàn)在,進入您的package.json
, 添加npm test
命令:
"scripts": { "test": "jest" }
如果您現(xiàn)在執(zhí)行npm test
命令,您應(yīng)該會看到測試運行并通過!
PASS __tests__/state-functions.test.js Addition ? knows that 2 and 2 make 4 (5ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 passed, 0 total Time: 3.11s
如果您曾經(jīng)使用過 Jasmine 或其他測試框架,Jest允許我們使用describe和it來嵌套測試。您使用多少嵌套取決于您。我喜歡嵌套我的測試,這樣所有傳遞給describe和it的描述字符串幾乎就像一個句子一樣。
當(dāng)涉及到實際斷言時,您需要將要測試的內(nèi)容包裝在expect()調(diào)用中,然后在其上調(diào)用斷言。在這種情況下,我們使用了toBe。您可以在Jest文檔中找到所有可用斷言的列表。toBe使用===來檢查給定值是否與測試的值匹配。通過本教程,我們將遇到一些Jest的斷言。
測試業(yè)務(wù)邏輯
我們將測試我們的第一個狀態(tài)函數(shù)toggleDone
。 toggleDone
接收當(dāng)前狀態(tài)和我們想要切換的todo的ID。每個todo都有一個完成屬性,而toggleDone應(yīng)該將其從true切換為false,反之亦然。 注意:如果您正在跟隨本文,確保已經(jīng)克隆了代碼庫,并將app
文件夾復(fù)制到包含您的__tests__
文件夾的同一目錄中。您還需要安裝所有應(yīng)用程序的依賴項(例如React)。在克隆存儲庫后運行npm install
即可確保全部安裝完成。
我將從app/state-functions.js
導(dǎo)入函數(shù)并設(shè)置測試的結(jié)構(gòu)開始。當(dāng)Jest允許您使用describe
和it
進行嵌套時,您也可以使用test,這通常更易于閱讀。test只是Jest it函數(shù)的別名,但有時可以使測試更易于閱讀,減少嵌套。例如,以下是我如何使用嵌套的describe
和it
調(diào)用編寫該測試的方法:
import { toggleDone } from '../app/state-functions'; describe('toggleDone', () => { describe('when given an incomplete todo', () => { it('marks the todo as completed', () => { }); }); });
這是test
用法:
import { toggleDone } from '../app/state-functions'; test('toggleDone completes an incomplete todo', () => { });
測試仍然很容易讀懂,但現(xiàn)在縮進少了一些。這主要取決于個人喜好;選擇您更喜歡的風(fēng)格即可。
現(xiàn)在我們可以編寫斷言了。首先,我們將創(chuàng)建我們的起始狀態(tài),然后將其傳遞到toggleDone
中,以及我們要切換的todo的ID。toggleDone
將返回我們的最終狀態(tài),然后我們可以對其進行斷言:
import { toggleDone } from "../app/state-functions"; test("tooggleDone completes an incomplete todo", () => { const startState = [{ id: 1, done: false, text: "Buy Milk" }]; const finState = toggleDone(startState, 1); expect(finState).toEqual([{ id: 1, done: true, text: "Buy Milk" }]); });
現(xiàn)在我使用toEqual
進行斷言。您應(yīng)該使用toBe
來測試原始值(例如字符串和數(shù)字),使用toEqual
測試對象和數(shù)組。toEqual
是專門用于處理數(shù)組和對象的,并且會遞歸檢查給定對象內(nèi)的每個字段或項目以確保其匹配。
有了這個,我們現(xiàn)在可以運行npm test
并看到我們的狀態(tài)函數(shù)測試通過:
PASS __tests__/state-functions.test.js ? tooggleDone completes an incomplete todo (9ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 passed, 0 total Time: 3.166s
配置熱更新
在更改測試文件后,手動再次運行npm test有點繁瑣
。 Jest最好的功能之一是其監(jiān)視模式,它會監(jiān)視文件更改并相應(yīng)地運行測試。它甚至可以根據(jù)更改的文件確定要運行的子集測試。它非常強大和可靠,您可以在監(jiān)視模式下運行Jest,并在整天編寫代碼時保留它。
要在監(jiān)視模式下運行它,您可以運行npm test -- watch
。在第一個--之后傳遞給npm test
的任何內(nèi)容都將直接傳遞給基礎(chǔ)命令。這意味著這兩個命令是等效的:
npm test -- --watch
jest --watch
我建議您在另一個終端窗口中將Jest保留運行,以便在本教程的其余部分中使用。
在繼續(xù)測試React組件之前,我們將在另一個狀態(tài)函數(shù)上編寫一個測試。在實際應(yīng)用程序中,我會編寫更多的測試,但是為了本教程的緣故,我將跳過其中一些?,F(xiàn)在,讓我們編寫一個測試,以確保我們的deleteTodo函數(shù)正常工作。在查看下面我的編寫方式之前,請嘗試自己編寫它并查看您的測試與我的測試有何不同。
請記住,您需要更新頂部的導(dǎo)入語句以導(dǎo)入deleteTodo
和toggleTodo
:
import { toggleDone, deleteTodo } from "../app/state-functions";
這是我編寫測試用例:
test('deleteTodo deletes the todo it is given', () => { const startState = [{ id: 1, done: false, text: 'Buy Milk' }]; const finState = deleteTodo(startState, 1); expect(finState).toEqual([]); });
這個測試與第一個測試并沒有太大的區(qū)別:我們設(shè)置起始狀態(tài),運行我們的函數(shù),然后對最終狀態(tài)進行斷言。如果您已在監(jiān)視模式下運行Jest,請注意它如何檢測到您的新測試并運行它,以及它運行速度的快速性!這是一種在寫測試時立即得到反饋的好方法。
上面的測試還演示了測試的過程,其中包括:
- 設(shè)置
- 執(zhí)行要測試的功能
- 對結(jié)果進行斷言
通過以這種方式布局測試,您會發(fā)現(xiàn)更容易跟蹤和處理它們。
現(xiàn)在我們已經(jīng)滿意測試我們的狀態(tài)函數(shù),讓我們繼續(xù)測試React組件。
測試 React 組件
值得注意的是,默認情況下,我實際上鼓勵您不要在React組件上編寫太多測試。您想要非常徹底測試的任何內(nèi)容,例如業(yè)務(wù)邏輯,都應(yīng)該從組件中提取出來并作為獨立的函數(shù)存在,就像我們之前測試的狀態(tài)函數(shù)一樣。盡管如此,有時測試一些React交互(例如,確保用戶單擊按鈕時調(diào)用特定函數(shù)并傳遞正確參數(shù))是很有用的。我們將從測試我們的React組件是否呈現(xiàn)正確的數(shù)據(jù)開始,然后再看看如何測試交互。
為了編寫測試,我們將安裝Enzyme
,這是由Airbnb
編寫的包裝庫,使測試React組件變得更加容易。
注意:自本文首次編寫以來,React團隊已經(jīng)轉(zhuǎn)向Enzyme
,并推薦使用[React Testing Library(RTL)](https://testing-library.com/docs/react-testing-library/intro)
。閱讀該頁面很值得一看。如果您正在維護已經(jīng)使用Enzyme測試的代碼庫,則無需放棄所有內(nèi)容并轉(zhuǎn)移,但對于新項目,我建議考慮RTL
。
除了Enzyme
之外,我們還需要安裝適用于我們使用的React版本的適配器。對于React v16,使用enzyme-adapter-react-16,但對于React v17,目前沒有官方適配器可用,因此我們必須使用非官方版本。請注意,該軟件包旨在作為臨時措施,直到正式支持發(fā)布,并將在那時被棄用。
npm install --save-dev enzyme @wojtekmaj/enzyme-adapter-react-17
我們需要對 Enzyme
進行少量設(shè)置。在項目的根目錄中,創(chuàng)建setup-tests.js
以下代碼并將其放入其中:
import { configure } from 'enzyme'; import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; configure({ adapter: new Adapter() });
然后,我們需要告訴Jest在執(zhí)行任何測試之前為我們運行此文件。我們可以通過配置setupFilesAfterEnv
選項來實現(xiàn)這一點。您可以將Jest配置放在自己的文件中,但我喜歡使用package.json
并將東西放在jest對象中,Jest也會自動檢測到它:
"jest": { "setupFilesAfterEnv": [ "./setup-tests.js" ] }
現(xiàn)在我們準(zhǔn)備編寫一些測試了!讓我們測試該Todo
組件是否在段落內(nèi)呈現(xiàn)其待辦事項的文本。首先我們將創(chuàng)建__tests__/todo.test.js
并導(dǎo)入我們的組件:
import Todo from '../app/todo'; import React from 'react'; import { mount } from 'enzyme'; test('Todo component renders the text of the todo', () => { });
從Enzyme
導(dǎo)入了mount
。mount
函數(shù)用于呈現(xiàn)我們的組件,然后允許我們檢查輸出并對其進行斷言。即使我們在Node
中運行測試,我們?nèi)匀豢梢跃帉懶枰狣OM的測試。這是因為Jest配置了jsdom
,一個在Node
中實現(xiàn)DOM的庫。這很好,因為我們可以編寫基于DOM的測試,而無需每次啟動瀏覽器進行測試。
可以使用mount
來創(chuàng)建我們的Todo
:
const todo = { id: 1, done: false, name: 'Buy Milk' }; const wrapper = mount( <Todo todo={todo} /> );
然后,我們可以調(diào)用wrapper.find
,給它一個CSS選擇器,以查找我們希望包含Todo文本的段落。這個API可能會讓您想起jQuery,這是有意設(shè)計的。這是一個在渲染的輸出中搜索匹配元素的非常直觀的API。
const p = wrapper.find('.toggle-todo');
最后,我們可以斷言其中的文本是Buy Milk
:
expect(p.text()).toBe('Buy Milk');
完整的測試用例是這樣的:
import Todo from '../app/todo'; import React from 'react'; import { mount } from 'enzyme'; test('TodoComponent renders the text inside it', () => { const todo = { id: 1, done: false, name: 'Buy Milk' }; const wrapper = mount( <Todo todo={todo} /> ); const p = wrapper.find('.toggle-todo'); expect(p.text()).toBe('Buy Milk'); });
現(xiàn)在我們有了一個測試,可以檢查我們是否成功地渲染了todos。 接下來,讓我們看一下如何使用Jest的spy功能來斷言具有特定參數(shù)的函數(shù)被調(diào)用。在我們的情況下很有用,因為我們有一個Todo組件,它被賦予了兩個函數(shù)作為屬性,當(dāng)用戶單擊按鈕或執(zhí)行交互時應(yīng)該調(diào)用這些函數(shù)。
在這個測試中,我們將斷言當(dāng)Todo
被單擊時,組件將調(diào)用它所賦予的doneChange
屬性:
test('Todo calls doneChange when todo is clicked', () => { });
我們希望有一個可以用來跟蹤其調(diào)用及其調(diào)用參數(shù)的函數(shù)。然后,我們可以檢查當(dāng)用戶單擊Todo時,doneChange
函數(shù)是否被調(diào)用,并且被調(diào)用時使用了正確的參數(shù)。幸運的是,Jest在功能上提供了這個功能來使用spy(間諜)
。spy
是一種函數(shù),其實現(xiàn)方式并不重要,您只需要關(guān)心它何時以及如何被調(diào)用??梢詫⑵湎胂鬄槟趥刹煸摵瘮?shù)。要創(chuàng)建一個spy
,我們調(diào)用jest.fn()
:
const doneChange = jest.fn();
這提供了一個函數(shù),我們可以對其進行監(jiān)視并確保其被正確調(diào)用。讓我們從使用正確的props來呈現(xiàn)我們的Todo
開始:
const todo = { id: 1, done: false, name: 'Buy Milk' }; const doneChange = jest.fn(); const wrapper = mount( <Todo todo={todo} doneChange={doneChange} /> );
接下來,我們可以再次查找我們的段落,就像在前面的測試中一樣:
const p = wrapper.find(".toggle-todo");
然后我們可以調(diào)用simulate
它來模擬用戶事件,并click
作為參數(shù)傳遞:
p.simulate('click');
現(xiàn)在我們只需要斷言我們的spy函數(shù)是否被正確調(diào)用。在這種情況下,我們期望它被調(diào)用時使用了ID為1
的todo。我們可以使用expect(doneChange).toBeCalledWith(1)
來斷言這一點,然后我們的測試就完成了!
test('TodoComponent calls doneChange when todo is clicked', () => { const todo = { id: 1, done: false, name: 'Buy Milk' }; const doneChange = jest.fn(); const wrapper = mount( <Todo todo={todo} doneChange={doneChange} /> ); const p = wrapper.find('.toggle-todo'); p.simulate('click'); expect(doneChange).toBeCalledWith(1); });
結(jié)論
Facebook很久以前就發(fā)布了Jest,但最近已經(jīng)被廣泛采用和改進。它已經(jīng)快速成為JavaScript開發(fā)人員的首選,而且它只會變得更好。如果你以前嘗試過Jest并且不喜歡它,我無法足夠地鼓勵你再次嘗試它,因為現(xiàn)在它基本上是一個完全不同的框架。它快速,重復(fù)運行規(guī)格非常出色,提供了出色的錯誤消息,并且有一個出色的表達式API,用于編寫良好的測試。
以上就是詳解如何使用Jest測試React組件的詳細內(nèi)容,更多關(guān)于Jest測試React組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在Ant Design Pro登錄功能中集成圖形驗證碼組件的方法步驟
這篇文章主要介紹了在Ant Design Pro登錄功能中集成圖形驗證碼組件的方法步驟,這里的登錄功能其實就是一個表單提交,實現(xiàn)起來也很簡單,具體實例代碼跟隨小編一起看看吧2021-05-05React18系列reconciler從0實現(xiàn)過程詳解
這篇文章主要介紹了React18系列reconciler從0實現(xiàn)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01react native基于FlatList下拉刷新上拉加載實現(xiàn)代碼示例
這篇文章主要介紹了react native基于FlatList下拉刷新上拉加載實現(xiàn)代碼示例2018-09-09