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