跳至主要內容

從 Enzyme 遷移

此頁面適用於有 Enzyme 使用經驗並試圖了解如何遷移到 React Testing Library 的開發人員。它不會深入探討如何遷移所有類型的測試,但對於那些正在比較 Enzyme 與 React Testing Library 的人來說,它確實有一些有用的資訊。

什麼是 React Testing Library?

React Testing Library 是一個名為 Testing Library 的開源專案的一部分。Testing Library 專案中還有其他幾個有用的工具和程式庫,您可以使用它們來編寫更簡潔且更有用的測試。除了 React Testing Library 之外,以下是一些專案的其他程式庫,可以幫助您:

  • @testing-library/jest-domjest-dom 提供一組自訂的 Jest 比對器,您可以使用它們來擴充 Jest。這些比對器使您的測試更具宣告性、更易於閱讀且更易於維護。

  • @testing-library/user-eventuser-event 嘗試模擬使用者與頁面上的元素互動時,瀏覽器中發生的真實事件。例如,userEvent.click(checkbox) 會變更核取方塊的狀態。

為什麼我應該使用 React Testing Library?

Enzyme 是一個強大的測試程式庫,其貢獻者為 JavaScript 社群做了很多。事實上,許多 React Testing Library 的維護者在開發和使用 React Testing Library 之前,都使用並貢獻了 Enzyme 多年。因此,我們要感謝 Enzyme 的貢獻者!

React Testing Library 的主要目的是透過以使用者使用元件的方式測試元件,來提高您對測試的信心。使用者不關心幕後發生什麼事,他們只會看到並與輸出互動。您將會透過根據元件輸出來編寫測試,而不是存取元件的內部 API 或評估其 state,來獲得更多信心。

React Testing Library 旨在解決許多開發人員在使用 Enzyme 編寫測試時所面臨的問題,Enzyme 允許(並鼓勵)開發人員測試實作細節。這樣做的測試最終會阻止您修改和重構元件,而無需變更其測試。結果,測試會降低開發速度和生產力。即使變更不會影響元件的輸出,每個小變更都可能需要重寫測試的某些部分。

在 React Testing Library 中重寫測試是值得的,因為您將會將會減慢速度的測試,換成能夠給您更多信心並從長遠來看提高您的生產力的測試。

如何從 Enzyme 遷移到 React Testing Library?

為確保成功遷移,我們建議逐步執行,方法是在同一個應用程式中並排執行兩個測試程式庫,然後一個一個地將您的 Enzyme 測試移植到 React Testing Library。這樣就可以遷移即使是大型且複雜的應用程式,而不會中斷其他業務,因為這項工作可以協作完成,並在一段時間內分散開來。

安裝 React Testing Library

首先,安裝 React Testing Library 和 jest-dom 輔助程式庫(您可以查看此頁面,以取得完整的安裝和設定指南)。

npm install --save-dev @testing-library/react @testing-library/jest-dom

將 React Testing Library 匯入到您的測試中

如果您使用 Jest (您可以使用其他測試框架),則您只需要將下列模組匯入到您的測試檔案中

// import React so you can use JSX (React.createElement) in your test
import React from 'react'

/**
* render: lets us render the component as React would
* screen: a utility for finding elements the same way the user does
*/
import {render, screen} from '@testing-library/react'

測試結構可以與您使用 Enzyme 編寫的測試相同

test('test title', () => {
// Your tests come here...
})

注意:您也可以將 describeit 區塊與 React Testing Library 一起使用。React Testing Library 不會取代 Jest,只會取代 Enzyme。我們建議使用 test,因為它有助於此:當您進行測試時,避免巢狀結構

從 Enzyme 到 React Testing Library 的基本遷移範例

要記住的一件事是,Enzyme 的功能與 React Testing Library 的功能之間沒有一對一的對應關係。許多 Enzyme 功能無論如何都會導致效率低下的測試,因此您習慣使用 Enzyme 的某些功能需要被捨棄(不再需要 wrapper 變數或 wrapper.update() 呼叫等)。

React Testing Library 有一些有用的查詢,可讓您存取元件的元素及其屬性。我們將展示一些典型的 Enzyme 測試,以及使用 React Testing Library 的替代方案。

假設我們有一個 Welcome 元件,它會顯示歡迎訊息。我們將查看 Enzyme 和 React Testing Library 測試,以了解如何測試此元件

React 元件

以下元件從 props 取得 name,並在 h1 元素中顯示歡迎訊息。它也有一個文字輸入,使用者可以將其變更為不同的名稱,並且範本會相應地更新。在 CodeSandbox 上查看即時版本。

const Welcome = props => {
const [values, setValues] = useState({
firstName: props.firstName,
lastName: props.lastName,
})

const handleChange = event => {
setValues({...values, [event.target.name]: event.target.value})
}

return (
<div>
<h1>
Welcome, {values.firstName} {values.lastName}
</h1>

<form name="userName">
<label>
First Name
<input
value={values.firstName}
name="firstName"
onChange={handleChange}
/>
</label>

<label>
Last Name
<input
value={values.lastName}
name="lastName"
onChange={handleChange}
/>
</label>
</form>
</div>
)
}

export default Welcome

測試 1:轉譯元件,並檢查 h1 值是否正確

Enzyme 測試

test('has correct welcome text', () => {
const wrapper = shallow(<Welcome firstName="John" lastName="Doe" />)
expect(wrapper.find('h1').text()).toEqual('Welcome, John Doe')
})

React Testing Library

test('has correct welcome text', () => {
render(<Welcome firstName="John" lastName="Doe" />)
expect(screen.getByRole('heading')).toHaveTextContent('Welcome, John Doe')
})

如您所見,測試非常相似。Enzyme 的 shallow 轉譯器不會轉譯子元件,因此 React Testing Library 的 render 方法更類似於 Enzyme 的 mount 方法。

在 React Testing Library 中,您不需要將 render 結果指派給變數 (即 wrapper)。您只需在 screen 物件上呼叫函數,即可存取轉譯的輸出。另一個值得了解的好處是,React Testing Library 會在每次測試後自動清理環境,因此您不需要在 afterEachbeforeEach 函數中呼叫 cleanup

您可能注意到的另一件事是 getByRole,它的引數為 'heading''heading'h1 元素的輔助使用角色。您可以在查詢文件頁面上深入了解它們。人們很快就會愛上 Testing Library 的其中一件事是,它如何鼓勵您編寫更方便存取的應用程式(因為如果它無法存取,那麼測試就會變得更困難)。

測試 2:輸入文字必須具有正確的值

在上面的元件中,輸入值會使用 props.firstNameprops.lastName 值初始化。我們需要檢查值是否正確。

Enzyme

test('has correct input value', () => {
const wrapper = shallow(<Welcome firstName="John" lastName="Doe" />)
expect(wrapper.find('input[name="firstName"]').value).toEqual('John')
expect(wrapper.find('input[name="lastName"]').value).toEqual('Doe')
})

React Testing Library

test('has correct input value', () => {
render(<Welcome firstName="John" lastName="Doe" />)
expect(screen.getByRole('form')).toHaveFormValues({
firstName: 'John',
lastName: 'Doe',
})
})

酷!它非常簡單方便,而且測試非常清楚,我們不需要談論太多。您可能注意到的一件事是,<form> 具有 role="form" 屬性,但它是什麼呢?

role 是一種輔助使用相關的屬性,建議使用以改善身心障礙人士的 Web 應用程式。某些元素具有預設的 role 值,您不需要為它們設定值,但某些其他元素 (例如 <div>) 沒有預設的 role 值。您可以使用不同的方法來存取 <div> 元素,但我們建議嘗試透過其隱含的 role 來存取元素,以確保身心障礙人士和使用螢幕閱讀器的人員可以存取您的元件。查詢文件的此部分可能會幫助您更好地了解概念。

<form> 元素必須具有 name 屬性,才能擁有 'form' 的隱含 role (根據規格的要求)。

React Testing Library 旨在測試使用者如何使用元件。使用者會根據按鈕、標題、表單和其他元素的角色來查看它們,而不是根據它們的 idclass 或元素標籤名稱。因此,當您使用 React Testing Library 時,您應該避免使用 document.querySelector API 來存取 DOM。(您 _可以_ 在測試中使用它,但不建議使用,原因如本段所述。)

React Testing Library 公開了一些方便的查詢 API,可幫助您有效地存取元件元素。您可以在這裡看到可用的查詢清單。如果您不確定在給定的情況下應該使用哪個查詢,我們有一個很棒的頁面,說明應該使用哪個查詢,所以請查看一下!

如果您仍然對使用哪個 React Testing Library 查詢有疑問,請查看 testing-playground.com 和隨附的 Chrome 擴充功能Testing Playground,其目的是讓開發人員在編寫測試時找到最佳查詢。它還可以幫助您找到選擇元素的最佳查詢。它允許您在 Chrome 開發人員工具中檢查元素階層,並提供如何選擇它們的建議,同時鼓勵良好的測試實務。

使用 act() 和 wrapper.update()

在 Enzyme 中測試非同步程式碼時,您通常需要呼叫 act() 才能正確執行測試。當您使用 React Testing Library 時,您大多數時候不需要明確呼叫 act(),因為它預設會使用 act() 包裝 API 呼叫。

update() 會將 Enzyme 元件樹快照與 React 元件樹同步,因此您可能會在 Enzyme 測試中看到 wrapper.update()。React Testing Library 沒有(或不需要)類似的方法,這對您來說是件好事,因為您需要處理的事情更少!

模擬使用者事件

有兩種方式可以使用 React Testing Library 模擬使用者事件。一種是使用 user-event 函式庫,另一種是使用 React Testing Library 中包含的 fireEventuser-event 實際上是建立在 fireEvent 之上(fireEvent 只是在給定的元素上呼叫 dispatchEvent)。通常建議使用 user-event,因為它可以確保所有事件都以典型使用者互動的正確順序觸發。這有助於確保您的測試與您的軟體實際使用方式相似。

若要使用 @testing-library/user-event 模組,請先安裝它

npm install --save-dev @testing-library/user-event @testing-library/dom

現在您可以將其匯入到您的測試中

import userEvent from '@testing-library/user-event'

為了示範如何使用 user-event 函式庫,假設我們有一個 Checkbox 元件,其中顯示一個核取方塊輸入和一個相關的標籤。我們想要模擬使用者點擊核取方塊的事件

import React from 'react'

const Checkbox = () => {
return (
<div>
<label htmlFor="checkbox">Check</label>
<input id="checkbox" type="checkbox" />
</div>
)
}

export default Checkbox

我們想要測試當使用者點擊核取方塊的相關標籤時,輸入的「checked」屬性是否已正確設定。讓我們看看如何為這種情況編寫測試

test('handles click correctly', async () => {
render(<Checkbox />)
const user = userEvent.setup()

// You can also call this method directly on userEvent,
// but using the methods from `.setup()` is recommended.
await user.click(screen.getByText('Check'))

expect(screen.getByLabelText('Check')).toBeChecked()
})

太棒了!

在測試中觸發類別方法 (wrapper.instance())

正如我們已經討論過的,我們不建議測試實作細節和使用者不會意識到的事情。我們的目標是以更像使用者的方式測試和互動元件。

如果您的測試使用`instance()``state()`,請知道您正在測試使用者不可能知道甚至不在意的事情,這會使您的測試更無法讓您有信心在使用者使用它們時它們能正常運作。— Kent C. Dodds

如果您不確定如何測試元件內部的事物,請退一步並思考:「使用者會做什麼來觸發此程式碼的執行?」然後讓您的測試執行該操作。

如何 shallow 渲染元件?

一般來說,您應該避免模擬元件。但是,如果需要,請嘗試使用 Jest 的模擬功能。如需更多資訊,請參閱常見問題解答