Testing Library 基础查询方法 #
查询方法概述 #
Testing Library 提供了三种类型的查询方法,每种方法有不同的行为和用途:
text
┌─────────────────────────────────────────────────────────────┐
│ 查询方法类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ getBy* → 同步获取,找不到则抛出错误 │
│ queryBy* → 同步获取,找不到返回 null │
│ findBy* → 异步获取,返回 Promise │
│ │
│ getAllBy* → 获取所有匹配元素,找不到则抛出错误 │
│ queryAllBy* → 获取所有匹配元素,找不到返回空数组 │
│ findAllBy* → 异步获取所有匹配元素,返回 Promise │
│ │
└─────────────────────────────────────────────────────────────┘
第一个测试 #
基本组件 #
jsx
function Greeting({ name }) {
return (
<div>
<h1>Hello, {name}!</h1>
<p>Welcome to our app.</p>
</div>
);
}
编写测试 #
jsx
import { render, screen } from '@testing-library/react';
import Greeting from './Greeting';
test('renders greeting with name', () => {
render(<Greeting name="World" />);
expect(screen.getByText('Hello, World!')).toBeInTheDocument();
expect(screen.getByText('Welcome to our app.')).toBeInTheDocument();
});
render 函数 #
基本用法 #
jsx
import { render } from '@testing-library/react';
const { container, unmount, rerender, asFragment } = render(<App />);
render 返回值 #
jsx
test('render returns useful utilities', () => {
const {
container, // 渲染的 DOM 容器
baseElement, // 基础 DOM 元素(通常是 document.body)
debug, // 调试函数,打印 DOM
rerender, // 重新渲染组件
unmount, // 卸载组件
asFragment, // 返回 DocumentFragment
getByText, // 查询方法(不推荐使用)
} = render(<App />);
expect(container).toBeInTheDocument();
});
debug 函数 #
jsx
test('debug example', () => {
const { debug } = render(<Greeting name="World" />);
debug();
debug(screen.getByText('Hello, World!'));
});
rerender 函数 #
jsx
test('rerender with new props', () => {
const { rerender } = render(<Greeting name="World" />);
expect(screen.getByText('Hello, World!')).toBeInTheDocument();
rerender(<Greeting name="React" />);
expect(screen.getByText('Hello, React!')).toBeInTheDocument();
});
unmount 函数 #
jsx
test('unmount removes component', () => {
const { unmount } = render(<Greeting name="World" />);
expect(screen.getByText('Hello, World!')).toBeInTheDocument();
unmount();
expect(screen.queryByText('Hello, World!')).not.toBeInTheDocument();
});
screen 对象 #
为什么使用 screen #
jsx
import { render, screen } from '@testing-library/react';
test('using screen is cleaner', () => {
render(<App />);
// ✅ 使用 screen - 推荐
screen.getByText('Hello');
// ❌ 不使用 screen - 不推荐
const { getByText } = render(<App />);
getByText('Hello');
});
screen 的优势 #
jsx
test('screen advantages', () => {
render(<App />);
// 1. 代码更简洁
screen.getByRole('button');
// 2. 可以使用 debug
screen.debug();
// 3. 可以使用 logTestingPlaygroundURL
screen.logTestingPlaygroundURL();
});
查询方法详解 #
getBy* 系列 #
用于断言元素存在:
jsx
test('getBy throws when not found', () => {
render(<App />);
// ✅ 元素存在时使用
expect(screen.getByText('Hello')).toBeInTheDocument();
// ❌ 元素不存在时会抛出错误
// screen.getByText('Not Found'); // Error!
});
queryBy* 系列 #
用于断言元素不存在:
jsx
test('queryBy returns null when not found', () => {
render(<App />);
// ✅ 断言元素不存在
expect(screen.queryByText('Error')).not.toBeInTheDocument();
// ✅ 条件判断
const error = screen.queryByText('Error');
if (error) {
// 处理错误
}
});
findBy* 系列 #
用于异步获取元素:
jsx
test('findBy waits for element', async () => {
render(<AsyncComponent />);
// ✅ 等待异步元素出现
const element = await screen.findByText('Loaded');
expect(element).toBeInTheDocument();
// ✅ 可以设置超时
await screen.findByText('Slow Load', {}, { timeout: 5000 });
});
getAllBy* 系列 #
获取多个匹配元素:
jsx
function List() {
return (
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Orange</li>
</ul>
);
}
test('getAllBy returns array', () => {
render(<List />);
const items = screen.getAllByRole('listitem');
expect(items).toHaveLength(3);
expect(items[0]).toHaveTextContent('Apple');
});
queryAllBy* 系列 #
获取多个元素或空数组:
jsx
test('queryAllBy returns empty array when not found', () => {
render(<App />);
const errors = screen.queryAllByRole('alert');
expect(errors).toHaveLength(0);
});
findAllBy* 系列 #
异步获取多个元素:
jsx
test('findAllBy waits for elements', async () => {
render(<AsyncList />);
const items = await screen.findAllByRole('listitem');
expect(items.length).toBeGreaterThan(0);
});
查询优先级 #
Testing Library 推荐按照以下优先级选择查询方法:
1. getByRole(最推荐) #
jsx
function Button() {
return <button>Submit</button>;
}
test('use getByRole for accessibility', () => {
render(<Button />);
// ✅ 最佳实践 - 使用 role
screen.getByRole('button', { name: /submit/i });
});
常用 ARIA Roles #
jsx
function Form() {
return (
<form>
<h1>Sign Up</h1>
<label htmlFor="email">Email</label>
<input id="email" type="email" />
<button type="submit">Submit</button>
</form>
);
}
test('common roles', () => {
render(<Form />);
screen.getByRole('heading', { name: /sign up/i });
screen.getByRole('textbox', { name: /email/i });
screen.getByRole('button', { name: /submit/i });
});
2. getByLabelText #
jsx
function LoginForm() {
return (
<form>
<label htmlFor="username">Username</label>
<input id="username" />
<label htmlFor="password">Password</label>
<input id="password" type="password" />
</form>
);
}
test('use getByLabelText for form inputs', () => {
render(<LoginForm />);
screen.getByLabelText('Username');
screen.getByLabelText('Password');
});
3. getByPlaceholderText #
jsx
function SearchInput() {
return <input placeholder="Search..." />;
}
test('use getByPlaceholderText for inputs', () => {
render(<SearchInput />);
screen.getByPlaceholderText('Search...');
});
4. getByText #
jsx
function Nav() {
return (
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
);
}
test('use getByText for text content', () => {
render(<Nav />);
screen.getByText('Home');
screen.getByText('About');
});
5. getByDisplayValue #
jsx
function ControlledInput() {
const [value, setValue] = useState('initial');
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
test('use getByDisplayValue for input values', () => {
render(<ControlledInput />);
screen.getByDisplayValue('initial');
});
6. getByAltText #
jsx
function Avatar() {
return <img src="/avatar.jpg" alt="User avatar" />;
}
test('use getByAltText for images', () => {
render(<Avatar />);
screen.getByAltText('User avatar');
});
7. getByTitle #
jsx
function Tooltip() {
return (
<span title="More information">
<svg>...</svg>
</span>
);
}
test('use getByTitle for title attribute', () => {
render(<Tooltip />);
screen.getByTitle('More information');
});
8. getByTestId(最后手段) #
jsx
function DynamicList() {
return (
<ul data-testid="dynamic-list">
<li data-testid="list-item-1">Item 1</li>
<li data-testid="list-item-2">Item 2</li>
</ul>
);
}
test('use getByTestId as last resort', () => {
render(<DynamicList />);
screen.getByTestId('dynamic-list');
});
查询选项 #
name 选项 #
jsx
function Buttons() {
return (
<div>
<button>Save</button>
<button>Cancel</button>
</div>
);
}
test('name option filters by accessible name', () => {
render(<Buttons />);
screen.getByRole('button', { name: 'Save' });
screen.getByRole('button', { name: 'Cancel' });
// 使用正则表达式
screen.getByRole('button', { name: /save/i });
});
hidden 选项 #
jsx
function HiddenElement() {
return (
<div>
<button style={{ display: 'none' }}>Hidden</button>
<button>Visible</button>
</div>
);
}
test('hidden option includes hidden elements', () => {
render(<HiddenElement />);
// 默认不包含隐藏元素
expect(screen.queryByRole('button', { name: 'Hidden' })).not.toBeInTheDocument();
// 使用 hidden: true 包含隐藏元素
screen.getByRole('button', { name: 'Hidden', hidden: true });
});
exact 选项 #
jsx
function Greeting() {
return <h1>Hello World</h1>;
}
test('exact option for matching', () => {
render(<Greeting />);
// 默认精确匹配
screen.getByText('Hello World');
// 关闭精确匹配
screen.getByText('Hello', { exact: false });
});
正则表达式匹配 #
基本用法 #
jsx
function UserCard({ name }) {
return <div>User: {name}</div>;
}
test('regex matching', () => {
render(<UserCard name="John Doe" />);
// 完全匹配
screen.getByText('User: John Doe');
// 部分匹配
screen.getByText(/John Doe/);
// 不区分大小写
screen.getByText(/john doe/i);
// 开头匹配
screen.getByText(/^User:/);
// 结尾匹配
screen.getByText(/Doe$/);
});
复杂匹配 #
jsx
function Price({ amount }) {
return <span>Price: ${amount.toFixed(2)}</span>;
}
test('complex regex patterns', () => {
render(<Price amount={99.99} />);
// 匹配价格格式
screen.getByText(/Price: \$\d+\.\d{2}/);
// 使用 RegExp 构造函数
const pattern = new RegExp(`Price: \\$${99.99}`);
screen.getByText(pattern);
});
自定义文本匹配 #
函数匹配器 #
jsx
function ProductList({ products }) {
return (
<ul>
{products.map(p => (
<li key={p.id}>{p.name} - ${p.price}</li>
))}
</ul>
);
}
test('function matcher', () => {
const products = [
{ id: 1, name: 'Apple', price: 1.99 },
{ id: 2, name: 'Banana', price: 0.99 },
];
render(<ProductList products={products} />);
// 使用函数匹配
screen.getByText((content, element) => {
return content.includes('Apple') && content.includes('1.99');
});
});
错误处理 #
元素未找到错误 #
jsx
test('element not found error', () => {
render(<App />);
// 抛出错误并显示可用的元素
screen.getByRole('button', { name: 'Non-existent' });
// Error: Unable to find an accessible element with the role "button"
// and name "Non-existent"
//
// Here are the accessible roles:
// heading:
// Name "Welcome":
// <h1 />
// ...
});
多个元素匹配错误 #
jsx
function MultipleButtons() {
return (
<div>
<button>Click</button>
<button>Click</button>
</div>
);
}
test('multiple elements error', () => {
render(<MultipleButtons />);
// 抛出错误,显示找到多个元素
screen.getByText('Click');
// Error: Found multiple elements with the text: Click
// (If this is intentional, use getAllBy* instead)
});
实用技巧 #
使用 within 限定范围 #
jsx
import { render, screen, within } from '@testing-library/react';
function Sidebar() {
return (
<aside>
<h2>Navigation</h2>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</aside>
);
}
test('within limits search scope', () => {
render(<Sidebar />);
const nav = screen.getByRole('navigation');
const homeLink = within(nav).getByRole('link', { name: 'Home' });
const aboutLink = within(nav).getByRole('link', { name: 'About' });
});
使用 container.querySelector #
jsx
test('container.querySelector for edge cases', () => {
const { container } = render(<App />);
// 仅在无法使用 Testing Library 查询时使用
const element = container.querySelector('.custom-class');
});
调试技巧 #
jsx
test('debugging techniques', () => {
render(<App />);
// 打印整个 DOM
screen.debug();
// 打印特定元素
screen.debug(screen.getByRole('button'));
// 生成 playground URL
screen.logTestingPlaygroundURL();
});
最佳实践总结 #
推荐做法 #
jsx
// ✅ 使用语义化查询
screen.getByRole('button', { name: /submit/i });
screen.getByLabelText(/email/i);
// ✅ 使用 screen 对象
render(<App />);
screen.getByText('Hello');
// ✅ 正确选择查询类型
screen.getByText('Hello'); // 断言存在
screen.queryByText('Error'); // 断言不存在
await screen.findByText('Loaded'); // 异步等待
避免做法 #
jsx
// ❌ 使用 data-testid 作为首选
screen.getByTestId('submit-button');
// ❌ 使用 container.querySelector
container.querySelector('button.submit');
// ❌ 使用错误的查询类型
screen.getByText('Error'); // 应该用 queryByText 断言不存在
// ❌ 不使用 screen
const { getByText } = render(<App />);
getByText('Hello');
下一步 #
现在你已经掌握了基础查询方法,接下来学习 查询方法详解 深入了解每种查询方法!
最后更新:2026-03-28