关于查询
概述
查询是测试库提供的用于查找页面上元素的方法。有多种类型的查询(“get”、“find”、“query”);它们之间的区别在于查询是否会抛出错误(如果没有找到元素),或者是否会返回 Promise 并重试。根据您选择的内容,不同的查询可能或多或少合适。请参阅优先级指南,了解有关如何使用语义查询以最易访问的方式测试页面的建议。
选择元素后,您可以使用事件 API 或user-event 来触发事件并模拟用户与页面的交互,或者使用 Jest 和jest-dom 来对元素进行断言。
有一些测试库辅助方法可与查询一起使用。当元素响应操作出现和消失时,异步 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,它在找到匹配给定查询的元素时解析。如果在默认超时时间 1000 毫秒后未找到任何元素或找到多个元素,则拒绝该 Promise。如果需要查找多个元素,请使用findAllBy
。
- 多个元素
getAllBy...
:返回查询的所有匹配节点的数组,如果未找到任何元素,则抛出错误。queryAllBy...
:返回查询的所有匹配节点的数组,如果未找到任何元素,则返回空数组 ([]
)。findAllBy...
:返回一个 Promise,它在找到任何匹配给定查询的元素时解析为元素数组。如果在默认超时时间1000
毫秒后未找到任何元素,则拒绝该 Promise。findBy
方法是getBy*
查询和waitFor
的组合。它们接受waitFor
选项作为最后一个参数(例如,await screen.findByText('text', queryOptions, waitForOptions)
)。
摘要表
查询类型 | 0 个匹配项 | 1 个匹配项 | >1 个匹配项 | 重试(异步/等待) |
---|---|---|---|---|
单个元素 | ||||
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 测试库中的基本查询要求您将 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 测试库导出的所有查询都接受 container
作为第一个参数。由于查询整个 document.body
非常常见,因此 DOM 测试库还导出一个 screen
对象,该对象包含预绑定到 document.body
的每个查询(使用within
功能)。React 测试库等包装器会重新导出 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 测试库
会自动规范化该文本。默认情况下,规范化包括从文本的开头和结尾修剪空白,以及将字符串中多个相邻的空白字符折叠为单个空格。
如果您想阻止这种规范化,或者提供替代的规范化(例如,删除 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"]')
浏览器扩展
您是否仍然不知道如何使用测试库查询?
有一个非常酷的 Chrome 浏览器扩展名为 Testing Playground,它可以帮助您找到选择元素的最佳查询。它允许您在浏览器的开发者工具中检查元素层次结构,并为您提供选择它们的建议,同时鼓励良好的测试实践。
游乐场
如果您想更熟悉这些查询,您可以尝试在 testing-playground.com 上使用它们。Testing Playground 是一个交互式沙箱,您可以在其中对自己的 html 运行不同的查询,并获得与上述规则匹配的视觉反馈。