---
url: /api/browser/react.md
---
# vitest-browser-react
社区 [`vitest-browser-react`](https://npmx.dev/package/vitest-browser-react) 包在 [浏览器模式](/guide/browser/) 中渲染 [React](https://react.dev/) 组件。
```jsx
import { render } from 'vitest-browser-react'
import { expect, test } from 'vitest'
import Component from './Component.jsx'
test('counter button increments the count', async () => {
const screen = await render()
await screen.getByRole('button', { name: 'Increment' }).click()
await expect.element(screen.getByText('Count is 2')).toBeVisible()
})
```
::: warning
该库的灵感来自 [`@testing-library/react`](https://github.com/testing-library/react-testing-library)。
如果你之前曾在测试中使用过 `@testing-library/react`,你可以继续使用它,但是 `vitest-browser-react` 包提供了一些 [浏览器模式](/guide/browser/) 独有的优势,而 `@testing-library/react` 缺乏这些优势:
`vitest-browser-react` 返回的 API 能与内置的 [定位器](/api/browser/locators)、[用户事件](/api/browser/interactivity) 和 [断言](/api/browser/assertions) 很好地交互:例如,Vitest 会自动重试元素直到断言成功,即使它在断言之间被重新渲染。
:::
该包暴露了两个入口点:`vitest-browser-react` 和 `vitest-browser-react/pure`。它们暴露了几乎相同的 API(`pure` 还暴露了 `configure`),但 `pure` 入口点不会添加一个处理器来在下一个测试开始前移除组件。
## render
```ts
export function render(
ui: React.ReactNode,
options?: ComponentRenderOptions,
): Promise
```
`render` 函数会记录一个 `react.render` 追踪标记,可在 [追踪视图](/guide/browser/trace-view) 中看到。
:::warning
请注意,`render` 是异步的,与其他包不同。这是为了正确支持 [`Suspense`](https://react.dev/reference/react/Suspense)。
```tsx
import { render } from 'vitest-browser-react'
const screen = render() // [!code --]
const screen = await render() // [!code ++]
```
:::
### 选项
#### container
默认情况下,Vitest 会创建一个 `div`,将其追加到 `document.body`,并将你的组件渲染在那里。如果你提供自己的 `HTMLElement` 容器,它不会被自动追加——你需要在 `render` 之前调用 `document.body.appendChild(container)`。
例如,如果你正在对 `tbody` 元素进行单元测试,它不能是 `div` 的子元素。在这种情况下,你可以指定一个 `table` 作为渲染容器。
```jsx
const table = document.createElement('table')
const { container } = await render(, {
// ⚠️ 在渲染前手动将元素追加到 `body`
container: document.body.appendChild(table),
})
```
#### baseElement
如果指定了 `container`,则默认为该值,否则默认为 `document.body`。它用作查询的基元素,也是当你使用 `debug()` 时打印的内容。
#### wrapper
传递一个 React 组件作为 `wrapper` 选项,使其渲染在内层元素周围。这对于为常见数据提供者创建可重用的自定义渲染函数最有用。例如:
```jsx
import React from 'react'
import { render } from 'vitest-browser-react'
import { ThemeProvider } from 'my-ui-lib'
import { TranslationProvider } from 'my-i18n-lib'
function AllTheProviders({ children }) {
return (
{children}
)
}
export function customRender(ui, options) {
return render(ui, { wrapper: AllTheProviders, ...options })
}
```
### 渲染结果
除了文档化的返回值外,`render` 函数还返回所有可用的 [定位器](/api/browser/locators),它们相对于 [`baseElement`](#baseelement),包括 [自定义定位器](/api/browser/locators#custom-locators)。
```tsx
const screen = await render()
await screen.getByRole('link', { name: 'Expand' }).click()
```
#### container
你渲染的 React 元素所在的包含 `div` DOM 节点(使用 `ReactDOM.render` 渲染)。这是一个普通的 DOM 节点,所以从技术上讲,你可以调用 `container.querySelector` 等来检查子元素。
:::danger
如果你发现自己使用 `container` 来查询渲染的元素,那么你应该重新考虑 旨在对你测试的组件将要进行的更改更具弹性。避免使用 `container` 来查询元素!
:::
#### baseElement
你的 React 元素在 `container` 中渲染的包含 DOM 节点。如果你在渲染选项中没有指定 `baseElement`,它将默认为 `document.body`。
当你想要测试的组件在容器 `div` 之外渲染某些内容时,这很有用,例如,当你想要快照测试你的 Portal 组件(它直接将 HTML 渲染在 body 中)时。
:::tip
`render` 返回的查询会查找 `baseElement`,所以你可以使用查询来测试你的 Portal 组件,而无需(手动指定) `baseElement`。
:::
#### locator
你 `container` 的 [定位器](/api/browser/locators)。仅将查询限定在你的组件范围内,或将其传递给其他断言很有用:
```jsx
import { render } from 'vitest-browser-react'
const { locator } = await render()
await locator.getByRole('button').click()
await expect.element(locator).toHaveTextContent('Hello World')
```
#### debug
```ts
function debug(
el?: HTMLElement | HTMLElement[] | Locator | Locator[],
maxLength?: number,
options?: PrettyDOMOptions,
): void
```
此方法是 `console.log(prettyDOM(baseElement))` 的快捷方式。它会将容器或指定元素的 DOM 内容打印到控制台。
#### rerender
```ts
function rerender(ui: React.ReactNode): Promise
```
同样会在 [追踪视图](/guide/browser/trace-view) 中记录一个 `react.rerender` 追踪标记。
最好测试正在执行 prop 更新的组件,以确保 props 被正确更新,从而避免在测试中依赖实现细节。也就是说,如果你更喜欢在测试中更新已渲染组件的 props,此函数可用于更新已渲染组件的 props。
```jsx
import { render } from 'vitest-browser-react'
const { rerender } = await render()
// re-render the same component with different props
await rerender()
```
#### unmount
```ts
function unmount(): Promise
```
同样会在 [追踪视图](/guide/browser/trace-view) 中记录一个 `react.unmount` 追踪标记。
这将导致渲染的组件被卸载。这对于测试当你的组件从页面移除时会发生什么很有用(例如测试你是否留下了悬空的事件处理器导致内存泄漏)。
```jsx
import { render } from 'vitest-browser-react'
const { container, unmount } = await render()
await unmount()
// your component has been unmounted and now: container.innerHTML === ''
```
#### asFragment
```ts
function asFragment(): DocumentFragment
```
返回你渲染组件的 `DocumentFragment`。如果你需要避免实时绑定并查看组件如何响应事件,这可能很有用。
## cleanup
```ts
export function cleanup(): Promise
```
移除所有通过 [`render`](#render) 渲染的组件。
## renderHook
```ts
export function renderHook(
renderCallback: (initialProps?: Props) => Result,
options: RenderHookOptions,
): Promise>
```
这是 `render` 的一个便利包装器,带有自定义测试组件。该 API 源于一种流行的测试模式,主要对发布 hooks 的库感兴趣。你应该优先使用 `render`,因为自定义测试组件会产生更具可读性和鲁棒性的测试,因为你想要测试的内容没有隐藏在抽象之后。
```jsx
import { renderHook } from 'vitest-browser-react'
test('returns logged in user', async () => {
const { result } = await renderHook(() => useLoggedInUser())
expect(result.current).toEqual({ name: 'Alice' })
})
```
### 选项
`renderHook` 接受与 [`render`](#render) 相同的选项,并增加了 `initialProps`:
它声明了首次调用时传递给渲染回调的 props。如果你调用 `rerender` 时不带 props,这些将不会被传递。
```jsx
import { renderHook } from 'vitest-browser-react'
test('returns logged in user', async () => {
const { result, rerender } = await renderHook((props = {}) => props, {
initialProps: { name: 'Alice' },
})
expect(result.current).toEqual({ name: 'Alice' })
await rerender()
expect(result.current).toEqual({ name: undefined })
})
```
:::warning
当结合使用 `renderHook` 与 `wrapper` 和 `initialProps` 选项时,`initialProps` 不会传递给 `wrapper` 组件。要为 `wrapper` 组件提供 props,请考虑如下解决方案:
```jsx
function createWrapper(Wrapper, props) {
return function CreatedWrapper({ children }) {
return {children}
}
}
// ...
await renderHook(() => {}, {
wrapper: createWrapper(Wrapper, { value: 'foo' }),
})
```
:::
`renderHook` 返回一些有用的方法和属性:
### 渲染 Hook 结果
#### result
持有渲染回调最近一次提交的返回值:
```jsx
import { useState } from 'react'
import { renderHook } from 'vitest-browser-react'
import { expect } from 'vitest'
const { result } = await renderHook(() => {
const [name, setName] = useState('')
React.useEffect(() => {
setName('Alice')
}, [])
return name
})
expect(result.current).toBe('Alice')
```
请注意,值保存在 `result.current` 中。将 result 视为最近一次提交值的 [ref](https://react.dev/learn/referencing-values-with-refs)。
#### rerender {#renderhooks-rerender}
使用新的 props 渲染之前渲染过的渲染回调:
```jsx
import { renderHook } from 'vitest-browser-react'
const { rerender } = await renderHook(({ name = 'Alice' } = {}) => name)
// re-render the same hook with different props
await rerender({ name: 'Bob' })
```
#### unmount {#renderhooks-unmount}
卸载测试 hook。
```jsx
import { renderHook } from 'vitest-browser-react'
const { unmount } = await renderHook(({ name = 'Alice' } = {}) => name)
await unmount()
```
## 扩展查询
要扩展定位器查询,请参阅 [`"自定义定位器"`](/api/browser/locators#custom-locators)。例如,要使 `render` 返回一个新的自定义定位器,请使用 `locators.extend` API 定义它:
```jsx {5-7,12}
import { locators } from 'vitest/browser'
import { render } from 'vitest-browser-react'
locators.extend({
getByArticleTitle(title) {
return `[data-title="${title}"]`
},
})
const screen = await render()
await expect.element(
screen.getByArticleTitle('Hello World')
).toBeVisible()
```
## 配置
你可以使用 `vitest-browser-react/pure` 中的 configure 方法来配置组件是否应该在严格模式下渲染:
```js
import { configure } from 'vitest-browser-react/pure'
configure({
// 默认禁用
reactStrictMode: true,
})
```
## 另请参阅
* [React Testing Library 文档](https://testing-library.com/docs/react-testing-library/intro)