React组件测试中应只Mock含副作用的子组件,用函数组件替代实现可控行为,按场景动态控制Mock状态,并通过传入行为函数解耦副作用以提升测试稳定性。

在 React 组件测试中,用 Mock 隔绝子组件的复杂副作用,核心是不让子组件执行真实逻辑(如发起请求、操作 DOM、订阅事件、调用 SDK),而是用轻量、可控的替身接管其渲染和行为。关键不在“完全不渲染”,而在“只保留测试所需的行为契约”。
只 Mock 有副作用的子组件,保留无副作用的结构
不是所有子组件都需要 Mock。纯展示型组件(如 Button、Card)若不触发副作用,可保留真实渲染;而含副作用的子组件(如 DataLoader、AnalyticsTracker、ThirdPartyMap)必须隔离。
- 用 jest.mock('./DataLoader') 替换整个模块导出,返回一个空 div 或仅含 props 转发的占位组件
- 避免使用 jest.unmock() 或手动 shallow render —— 容易漏掉深层副作用
- 若子组件通过 props 传入回调(如 onLoad),Mock 版本仍需接收并调用它,以验证父组件逻辑是否正确触发
用函数组件 Mock 替代类组件或第三方库
比起 mock 整个模块,更推荐用自定义函数组件替代,它更直观、可调试、易复用。
根据项目规范生成 React 和 Vue 组件,包括 TypeScript 类型定义、Ant Design 5 集成、Tailwind CSS 样式及 Vitest 测试文件。当用户提出创建、生成或搭建前端组件的需求时启用,例如:“帮我写一个表格组件”、“生成一个弹窗”、“create a form component”、“写一个列表页”、“生成一个下拉选择器”。该能力在以下任一场景触发:涉及 React/TSX/Vue 组件生成、UI 组件创建,或为组件生成测试文件。
- 例如,替换一个依赖 WebSocket 的实时通知组件:
const MockNotification = () =>[mocked]; - 在测试文件顶部直接覆盖导入:
jest.mock('./Notification', () => ({ Notification: MockNotification })); - 这样既跳过连接逻辑,又保留在 DOM 中的定位能力(可用 screen.getByTestId 断言存在)
按测试场景控制 Mock 行为,而非写死返回值
同一个子组件在不同测试中可能需要不同响应:加载中、成功、失败、延迟等。硬编码返回值会让测试僵化。
- 在 Mock 组件内用闭包或 useRef 管理状态,例如:
let mockState = 'success';
const MockApiCaller = ({ onResult }) => {
useEffect(() => { onResult?.(mockState === 'error' ? new Error('timeout') : { data: 'ok' }); }, []);
return ;
};
MockApiCaller.reset = () => { mockState = 'success'; };
MockApiCaller.failNext = () => { mockState = 'error'; }; - 测试前调用 MockApiCaller.failNext(),再渲染父组件,即可验证错误 UI
把副作用抽离到可注入的 Hook 或 prop,测试时直接替换
最可持续的方式,是从设计上解耦副作用。不要让子组件自己调用 fetch 或 SDK.init(),而是由父组件传入行为函数。
- 子组件签名改为:
- 测试时传入:
render(Promise.resolve([{ id: 1 }])} />); - 这样无需 Mock 模块,也不依赖 jest,测试更轻、更稳定、更接近真实使用方式










