關於查詢
概述
查詢是 Testing Library 提供您在頁面上尋找元素的方法。有幾種查詢類型(「get」、「find」、「query」);它們之間的區別在於,如果找不到任何元素,查詢是否會拋出錯誤,或者是否會返回 Promise 並重試。根據您選擇的頁面內容,不同的查詢可能更適合或更不適合。請參閱優先順序指南,以獲取有關如何使用語意查詢以最方便存取的方式測試頁面的建議。
選擇元素後,您可以使用事件 API 或 user-event 來觸發事件並模擬使用者與頁面的互動,或使用 Jest 和jest-dom 來對元素進行斷言。
有一些 Testing Library 輔助方法可與查詢一起使用。當元素響應動作而出現和消失時,可以使用非同步 API,如 waitFor
或 findBy
查詢,來等待 DOM 中的變更。若要僅尋找特定元素的子元素,您可以使用 within
。如有必要,您還可以設定一些選項,例如重試的逾時時間和預設 testID 屬性。
範例
import {render, screen} from '@testing-library/react' // (or /dom, /vue, ...)
test('should show login form', () => {
render(<Login />)
const input = screen.getByLabelText('Username')
// Events and assertions...
})
查詢類型
- 單一元素
getBy...
:傳回符合查詢的節點,如果找不到任何符合的元素或找到多個符合的元素,則會拋出描述性錯誤(如果預期有多個元素,請改用getAllBy
)。queryBy...
:傳回符合查詢的節點,如果找不到任何符合的元素,則傳回null
。這對於斷言不存在的元素很有用。如果找到多個符合的元素,則會拋出錯誤(如果這可以接受,請改用queryAllBy
)。findBy...
:傳回 Promise,當找到符合指定查詢的元素時,該 Promise 會解析。如果在預設逾時時間 1000 毫秒後找不到任何元素,或找到多個元素,則會拒絕該 Promise。如果需要尋找多個元素,請使用findAllBy
。
- 多個元素
getAllBy...
:傳回符合查詢的所有節點的陣列,如果找不到任何符合的元素,則會拋出錯誤。queryAllBy...
:傳回符合查詢的所有節點的陣列,如果找不到任何符合的元素,則傳回空陣列 ([]
)。findAllBy...
:傳回 Promise,當找到任何符合指定查詢的元素時,該 Promise 會解析為元素陣列。如果在預設逾時時間1000
毫秒後找不到任何元素,則會拒絕該 Promise。findBy
方法是getBy*
查詢和waitFor
的組合。它們接受waitFor
選項作為最後一個引數(即await screen.findByText('text', queryOptions, waitForOptions)
)。
摘要表
查詢類型 | 0 個符合項 | 1 個符合項 | >1 個符合項 | 重試 (非同步/Await) |
---|---|---|---|---|
單一元素 | ||||
getBy... | 拋出錯誤 | 傳回元素 | 拋出錯誤 | 否 |
queryBy... | 傳回 null | 傳回元素 | 拋出錯誤 | 否 |
findBy... | 拋出錯誤 | 傳回元素 | 拋出錯誤 | 是 |
多個元素 | ||||
getAllBy... | 拋出錯誤 | 傳回陣列 | 傳回陣列 | 否 |
queryAllBy... | 傳回 [] | 傳回陣列 | 傳回陣列 | 否 |
findAllBy... | 拋出錯誤 | 傳回陣列 | 傳回陣列 | 是 |
優先順序
根據指導原則,您的測試應盡可能類似於使用者與您的程式碼(元件、頁面等)互動的方式。考慮到這一點,我們建議依此優先順序:
- 所有人皆可存取的查詢 反映視覺/滑鼠使用者以及使用輔助技術的使用者的體驗的查詢。
getByRole
:這可用於查詢輔助功能樹狀結構中顯示的每個元素。使用name
選項,您可以依其可存取名稱篩選傳回的元素。這應該是您所有項目的首選。您幾乎沒有任何無法用此取得的東西(如果您無法取得,則表示您的 UI 可能無法存取)。通常,這會與name
選項一起使用,如下所示:getByRole('button', {name: /submit/i})
。查看角色清單。getByLabelText
:此方法非常適合用於表單欄位。當瀏覽網站表單時,使用者會使用標籤文字尋找元素。此方法模擬該行為,因此它應該是您的首選。getByPlaceholderText
:預留位置不是標籤的替代品。但如果這是您擁有的全部,則它比其他替代方案更好。getByText
:在表單之外,文字內容是使用者尋找元素的主要方式。此方法可用於尋找非互動式元素(如 div、span 和段落)。getByDisplayValue
:當使用填寫的值瀏覽頁面時,表單元素的目前值可能會很有用。
- 語意查詢 符合 HTML5 和 ARIA 的選取器。請注意,與這些屬性互動的使用者體驗在瀏覽器和輔助技術之間差異很大。
getByAltText
:如果您的元素支援alt
文字(img
、area
、input
和任何自訂元素),則可以使用此方法尋找該元素。getByTitle
:螢幕閱讀器不會一致地讀取 title 屬性,且預設對有視覺的使用者不可見。
- 測試 ID
getByTestId
:使用者無法看到(或聽到)這些內容,因此僅建議用於無法依角色或文字比對,或沒有意義的情況(例如,文字是動態的)。
使用查詢
DOM Testing Library 的基本查詢要求您傳遞 container
作為第一個引數。當您使用它們轉譯元件時,Testing Library 的大多數框架實作會提供這些查詢的預先繫結版本,這表示您不必提供 container。此外,如果您只想查詢 document.body
,則可以使用 screen
匯出,如下所示(建議使用 screen
)。
查詢的主要引數可以是字串、規則運算式或函式。還有一些選項可以調整如何剖析節點文字。如需關於可以傳遞給查詢的檔案,請參閱 TextMatch。
假設有下列 DOM 元素(可以由 React、Vue、Angular 或純 HTML 程式碼轉譯)
<body>
<div id="app">
<label for="username-input">Username</label>
<input id="username-input" />
</div>
</body>
您可以使用查詢來尋找元素(在此範例中為 byLabelText)
import {screen, getByLabelText} from '@testing-library/dom'
// With screen:
const inputNode1 = screen.getByLabelText('Username')
// Without screen, you need to provide a container:
const container = document.querySelector('#app')
const inputNode2 = getByLabelText(container, 'Username')
queryOptions
您可以傳遞具有查詢類型的 queryOptions
物件。請參閱每個查詢類型的文件,以查看可用的選項,例如 byRole API。
screen
DOM Testing Library 匯出的所有查詢都接受 container
作為第一個引數。由於查詢整個 document.body
非常常見,DOM Testing Library 還匯出一個 screen
物件,其中包含每個預先繫結至 document.body
的查詢(使用 within
功能)。諸如 React Testing Library 之類的包裝函式會重新匯出 screen
,因此您可以像使用它的方式一樣使用它。
以下是如何使用它
- 原生
- React
- Angular
- Cypress
import {screen} from '@testing-library/dom'
document.body.innerHTML = `
<label for="example">Example</label>
<input id="example" />
`
const exampleInput = screen.getByLabelText('Example')
import {render, screen} from '@testing-library/react'
render(
<div>
<label htmlFor="example">Example</label>
<input id="example" />
</div>,
)
const exampleInput = screen.getByLabelText('Example')
import {render, screen} from '@testing-library/angular'
await render(`
<div>
<label for="example">Example</label>
<input id="example" />
</div>
`)
const exampleInput = screen.getByLabelText('Example')
cy.findByLabelText('Example').should('exist')
注意
您需要全域 DOM 環境才能使用
screen
。如果您使用 jest,且將 testEnvironment 設定為jsdom
,則全域 DOM 環境將可供您使用。如果您使用
script
標籤載入測試,請確保它在body
之後出現。如需範例,請參閱此處。
TextMatch
大多數的查詢 API 接受 TextMatch
作為參數,這表示參數可以是字串、正規表示式或一個簽名為 (content?: string, element?: Element | null) => boolean
的函式,當匹配時返回 true
,不匹配時返回 false
。
TextMatch 範例
假設有以下 HTML
<div>Hello World</div>
會找到 div
// Matching a string:
screen.getByText('Hello World') // full string match
screen.getByText('llo Worl', {exact: false}) // substring match
screen.getByText('hello world', {exact: false}) // ignore case
// Matching a regex:
screen.getByText(/World/) // substring match
screen.getByText(/world/i) // substring match, ignore case
screen.getByText(/^hello world$/i) // full string match, ignore case
screen.getByText(/Hello W?oRlD/i) // substring match, ignore case, searches for "hello world" or "hello orld"
// Matching with a custom function:
screen.getByText((content, element) => content.startsWith('Hello'))
不會找到 div
// full string does not match
screen.getByText('Goodbye World')
// case-sensitive regex with different case
screen.getByText(/hello world/)
// function looking for a span when it's actually a div:
screen.getByText((content, element) => {
return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello')
})
精確度
接受 TextMatch
的查詢也接受一個物件作為最後一個參數,該物件可以包含影響字串匹配精確度的選項
exact
:預設值為true
;匹配完整字串,區分大小寫。當為 false 時,匹配子字串且不區分大小寫。- 當與
regex
或function
參數一起使用時,它沒有效果。 - 在大多數情況下,使用正規表示式而不是字串並結合
{ exact: false }
可以讓你更精確地控制模糊匹配,因此應該優先選擇。
- 當與
normalizer
:一個可選的函式,用於覆寫正規化行為。請參閱Normalization
。
正規化
在對 DOM 中的文字執行任何匹配邏輯之前,DOM Testing Library
會自動正規化該文字。預設情況下,正規化包括修剪文字開頭和結尾的空白,以及將字串內多個相鄰的空白字元摺疊為單個空格。
如果您想要防止該正規化,或提供替代的正規化(例如,移除 Unicode 控制字元),您可以在選項物件中提供一個 normalizer
函式。此函式將接收一個字串,並預期返回該字串的正規化版本。
注意
為
normalizer
指定一個值會取代內建的正規化,但您可以呼叫getDefaultNormalizer
來取得內建的正規化器,以便調整該正規化或從您自己的正規化器中呼叫它。
getDefaultNormalizer
接受一個選項物件,允許選擇行為
trim
:預設值為true
。修剪開頭和結尾的空白。collapseWhitespace
:預設值為true
。將內部空白(換行符號、製表符、重複空格)摺疊為單個空格。
正規化範例
要對文字執行匹配而不進行修剪
screen.getByText('text', {
normalizer: getDefaultNormalizer({trim: false}),
})
要覆寫正規化以移除一些 Unicode 字元,同時保留一些(但不是全部)內建的正規化行為
screen.getByText('text', {
normalizer: str =>
getDefaultNormalizer({trim: false})(str).replace(/[\u200E-\u200F]*/g, ''),
})
手動查詢
除了測試庫提供的查詢之外,您可以使用標準的 querySelector
DOM API 來查詢元素。請注意,不建議將其作為逃生艙口來依類別或 ID 查詢,因為它們對使用者是不可見的。如果必須這樣做,請使用 testid,以明確您要退回到非語意查詢的意圖,並在 HTML 中建立穩定的 API 合約。
// @testing-library/react
const {container} = render(<MyComponent />)
const foo = container.querySelector('[data-foo="bar"]')
瀏覽器擴充功能
您仍然對如何使用 Testing Library 查詢有問題嗎?
有一個非常酷的 Chrome 瀏覽器擴充功能名為 Testing Playground,它可以幫助您找到選擇元素的最佳查詢。它允許您在瀏覽器的開發人員工具中檢查元素層次結構,並為您提供如何選擇它們的建議,同時鼓勵良好的測試實踐。
Playground
如果您想更熟悉這些查詢,您可以在 testing-playground.com 上試用它們。Testing Playground 是一個互動式沙箱,您可以在其中針對自己的 HTML 運行不同的查詢,並獲得符合上述規則的視覺回饋。