示例来自微信:  before: - firefox:  - edge:  after: - firefox:   - edge:  - 使用 Lexical + OverlayScrollbars 实现统一可控的滑动条样式, 取代原来的textarea Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/149 Co-authored-by: 王德宇 <kvtodev@outlook.com> Co-committed-by: 王德宇 <kvtodev@outlook.com>
5.0 KiB
React 组件测试准则
更新时间:2026-08-07
背景
当前前端测试已经覆盖大量 React 组件、hook 和页面流程,但部分用例把组件内部状态、DOM 探针、按钮顺序、图标 class 或完整文案当成契约。这样的测试能快速发现改动,却也会让 UI 结构、交互文案和实现拆分变得很难迭代。
后续新增或重写 React 测试时,默认按本准则选择断言层级。已有测试不要求一次性批量迁移;当某个测试因正常重构频繁破碎,或本次任务正好修改该区域,就顺手收紧到稳定契约。
分层口径
-
用户流程测试验证用户可感知结果。
优先使用 Testing Library 的
userEvent、role、label、可见弹窗、URL、提交结果、错误提示和外部 callback。测试名应描述业务行为,不描述组件内部状态机步骤。 -
稳定契约测试验证对外边界。
对请求 DTO、callback payload、路由变化、持久化边界和后端回包映射,使用关键字段断言或
expect.objectContaining(...)。对象仍在演化时,不要断言完整对象、完整数组顺序或所有默认字段。 -
模型和 hook 测试验证纯逻辑。
复杂状态机优先沉到 model 或 hook 的公开返回契约中测试。hook 测试使用
renderHook直接调用公开方法和读取公开状态,不额外制造data-testid仪表盘组件来拼接内部字段。
避免的写法
- 不为了读取内部状态新建测试专用 DOM,例如
<span data-testid="active-id">、textContent拼接内部数组或状态名。 - 不把图标实现、第三方库 class、DOM 层级、完整按钮顺序当成稳定契约,除非产品明确要求该顺序或可访问语义本身就是行为。
- 不用精确长文案锁死可变提示词、分享文案、错误文案或 UI 标签;需要断言时只断言稳定语义片段,或改断言 payload / 状态码 / 目标 callback。
- 不在页面级测试里 mock 出一套与真实页面差异很大的“假壳”,再把假壳内部状态当作用户验收结果。页面壳测试可以保留,但应尽量断言 URL、标题、公开 callback 和真实可见行为。
推荐写法
- 交互优先用
userEvent,只有测试低层 pointer / wheel / drag 等浏览器事件细节时再用fireEvent。 - 查询优先用 role / label / alt / text 的用户语义;只有无可访问语义的画布参考线、不可见测量节点或渲染边界,才使用
data-testid。 data-testid名称必须描述用户或稳定渲染边界,例如image-canvas-editor-snap-guide-vertical;不要描述 React 私有 state 名称。- 测试用 fixture 只包含本行为需要的字段。演化中的 payload 使用
expect.objectContaining(...)或 helper 生成默认对象,避免一处契约加字段导致大量无关用例碎裂。 - 当测试是为防止历史回归,应在测试名或邻近注释中说明防的是什么行为,而不是记录实现步骤。
- 同一用例既要验证定时器调度参数,又要断言确定的中间帧或中间状态时,必须 mock 定时器回调或使用可控假时钟;不得让真实墙上时间在异步交互期间推进被断言的状态,否则本地通过的用例会在较慢 CI 中偶发失败。
- 测试 React effect 中注册的事件监听时,触发事件前先用可观测的 listener 调用确认注册已完成;异步请求已开始应用有界
waitFor断言确认,不要用无界手工 Promise 等待一次性信号。解除挂起请求时将 Promise 收尾纳入异步act,确保后续 React 更新在断言前已冲刷。 - 公共
AutoGrowTextArea使用 Lexicalcontenteditable,业务测试通过setPlainTextEditorValue写入文本、通过getPlainTextEditorHost断言外层样式;占位文案是独立渲染节点,不读取原生placeholder属性,也不对编辑根触发 textarea 专属的change事件。 - 异步请求失败时,错误状态与调用方的草稿 / 附件恢复可能分属连续两次 React 更新;用例必须对最终恢复结果使用有界
waitFor,不能把错误文案刚出现的中间帧当成恢复已经完成。
试点调整
src/components/image-editor/useCanvasGenerationDialogs.test.tsx 已从测试专用 DOM 仪表盘改为 renderHook,直接验证 hook 公开契约:打开、归档、激活、更新、删除、恢复和 ID 递增。
src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx 已去掉图标 class 和完整按钮顺序快照式断言,保留用户可操作按钮、工具切换 callback、可访问 pressed 状态和 hover / focus 打开选项的契约。
验证
修改 React 测试后,优先运行触达文件的定向测试,例如:
npm run test -- src/components/image-editor/useCanvasGenerationDialogs.test.tsx src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx --reporter=dot
涉及共享组件、路由壳或跨页面交互时,再追加对应页面测试、npm run typecheck、npm run check:encoding 和 git diff --check。