一、核心原则:测试用户行为,而非实现细节
的核心哲学是让你的测试尽可能模拟用户的使用方式。你应当测试的是“用户能看到什么、能点击什么、输入后会得到什么结果”,而不是测试组件的内部状态、生命周期或私有方法。
> 官方文档明确指出:“The more your tests the way your is used, the more they can give you.” —— 官方文档
遵循这一原则,你写出的测试将更稳定、更易维护,并且能在重构时提供真正的信心。
二、最佳实践清单
1. 使用正确的查询方法,优先级按官方推荐排序
提供多种查询方法,按照推荐优先级排序:
| 优先级 | 查询方法 | 示例 | 推荐理由 |
|---|---|---|---|
| 1 | |
('', { name: //i }) |
最贴近可访问性,用户和屏幕阅读器都依赖角色 |
| 2 | |
(//i) |
表单控件最自然的选择,用户通过标签找到字段 |
| 3 | |
(//i) |
辅助定位输入框,但不如标签可靠 |
| 4 | |
('') |
用于非交互元素,用户能看到文本 |
| 5 | |
(' value') |
针对已填充的表单值 |
| 6 | |
(//i) |
用于图像 |
| 7 | |
('close') |
辅助用途,但不如角色可靠 |
| 8 | |
('-') |
最后选择,仅在无法通过角色或文本定位时使用 |
> 来源: 官方文档 – 查询优先级
2. 优先使用 getBy<>,仅在预期不存在时用 </>,异步场景用 <>
getBy</>:同步查找元素,若未找到则立即抛出错误。适用于元素必定存在的场景。
<>:同步查找,未找到时返回 null。适用于断言元素不存在的场景。
</>:返回一个 ,结合 等待元素出现。适用于异步出现的元素。
// ✅ 正确:元素一定会出现
const = .('', { name: //i });
// ✅ 正确:断言元素不存在
(.('...')).not.();
// ✅ 正确:等待异步元素
const modal = await .('');
3. 使用 而非 模拟用户交互
只会触发单一事件,而 会模拟完整的用户交互行为(如点击时触发 、、click 等一系列事件)。
> 官方推荐使用 @-/user-event 替代 。 —— 生态文档
from '@-/user-event';
// ✅ 推荐
await .click(.(''));
// ❌ 不推荐(除非极特殊场景)
.click(.(''));
4. 异步操作统一使用 或 <>,避免使用 act 直接包裹
当需要等待某个元素出现或状态变化时,优先使用 </> 或 ,而不是手动 act。
// ✅ 推荐
const = await .('Saved');
// ✅ 或使用
await (() => {
(.('Saved')).();
});
> <> 内部已经封装了 ,更简洁。 —— 异步文档
5. 始终使用 简化代码
从 @-/react 中导入 ,避免每次解构 getBy...,使测试更清晰、易于维护。
{ , } from '@-/react';
// ✅ 推荐
(< />);
const = .('');
// ❌ 不推荐
const { } = (< />);
const = ('');
6. 保持测试独立,每个测试只验证一个行为
每个 it/test 块应该只测试一个功能点。
测试之间不应共享状态(使用 重置)。
避免在测试中过度设置全局环境。
// ✅ 好的测试结构
('', () => {
(() => {
(< />);
});
it('应该显示用户名和密码输入框', () => {
(.(//i)).();
(.(//i)).();
});
it('提交时应在密码错误时显示错误消息', async () => {
await .type(.(//i), 'test');
await .type(.(//i), 'wrong');
await .click(.('', { name: //i }));
(await .(/ /i)).();
});
});
7. 自定义 函数包装常用
如果你的应用依赖多个 (如 Redux、、Theme),可以创建自定义的 函数来减少重复代码。
// test-utils.js
{ } from '@-/react';
{ } from 'react--dom';
{ } from 'react-redux';
store from './store';
const = (ui, = {}) =>
(ui, {
: ({ }) => (
< store={store}>
<>
{}
</>
</>
),
...,
});
from '@-/react';
{ as };
然后在测试中使用:
{ , } from './test-utils';
8. 使用 jest-dom 或 -dom 的匹配器增强断言
安装 @-/jest-dom(Jest)或 @-/-dom(),获得语义化的断言方法,如 ()、()、() 等。
'@-/jest-dom';
// 清晰的断言
(.('')).();
(.('')).('hello');
9. 避免测试实现细节
不要测试组件内部状态(如 的值)。
不要测试生命周期方法(如 内部调用了什么 API,而是测试 UI 最终呈现的结果)。
不要直接测试 Redux ,而是通过用户交互验证状态变化。
// ❌ 错误:测试内部状态
(.state().).toBe(true);
// ✅ 正确:测试用户看到的效果
(.('')).();
10. 为异步加载提供清晰的 选项
当使用 时,设置合理的 和 ,避免因网络慢导致偶发失败。
await (
() => {
(.('Data ')).();
},
{ : 3000, : 100 }
);
三、常见误区与解决方案
| 误区 | 后果 | 解决方案 |
|---|---|---|
过度使用 |
测试与 DOM 结构耦合,重构时易失败 | 优先使用 + 可访问性属性 |
在测试中使用 |
不稳定,依赖真实等待时间 | 使用 或 * |
| 在同一个测试中设置多个交互 | 难以定位失败原因,测试职责不清 | 拆分为多个独立测试用例 |
忽略 的异步特性 |
事件可能未完全触发,导致断言失效 | 始终 await 交互方法 |
直接使用 . |
绕过了 的查询原则,导致测试脆弱 | 改用 . 等官方查询 |
四、总结
的最佳实践核心可以归纳为三句话:
1. 测试像用户一样操作:用 、 等用户能感知的方式定位元素。
2. 模拟真实交互:用 代替 ,用 处理异步。
3. 避免实现细节:不测试内部状态,只测试渲染结果和交互效果。
遵循这些实践,你的前端测试将更稳定、更易维护,真正成为保障代码质量的可靠防线。
参考资料
官方文档:
Kent C. Dodds 的博客《 》:
@-/jest-dom 匹配器列表:

