如何使用Jest/RTL mock函数组件ref属性覆盖测试边界分支
你之前mock useRef 不生效的核心原因:React在DOM节点挂载完成后,会自动给传入的ref对象的current属性赋值为对应的DOM实例,如果你只是简单mock useRef 返回{current: null},等<img>挂载完成后ref.current还是会被React自动改成img节点,自然走不到空值判断分支。
以下是三种可落地的方案,优先推荐第一种:
方案1:抽离辅助函数单独测试(最推荐)
helperFunction本身是独立的逻辑判断,完全不需要依赖组件渲染流程来测试,直接把它从组件内部抽离导出,单独传参测试即可100%覆盖分支:
// 组件文件里把helperFunction导出 export function helperFunction(node, ref) { if (!ref || !ref.current) return; // ...do something } export default function Component() { const imgRef = useRef(null) return ( <AlignComponent onAlign={(node) => {helperFunction(node, imgRef)}}> <img ref={imgRef} /> </AlignComponent> ) }
测试代码直接写:
import { helperFunction } from './component-path' test('helperFunction should early return when ref is invalid', () => { const mockNode = document.createElement('div') // 覆盖!ref分支 expect(helperFunction(mockNode, null)).toBeUndefined() // 覆盖!ref.current分支 expect(helperFunction(mockNode, { current: null })).toBeUndefined() })
这个方案没有任何hack逻辑,完全符合单元测试的隔离原则,不会和React的内部ref赋值机制冲突,覆盖率统计完全准确。
方案2:拦截onAlign回调,手动清空ref后触发
如果你不想改动组件代码抽离函数,可以在测试环境拦截子组件的onAlign回调,手动把ref的current值置空后再执行回调,就能走到空值分支。
以React Testing Library + Jest为例:
// 先mock代码里嵌套的那个触发onAlign的组件,拿到onAlign回调的引用 jest.mock('./align-component', () => ({ __esModule: true, default: ({ children, onAlign }) => { // 把回调挂到全局,测试里可以手动触发 globalThis.testTriggerAlign = onAlign return <div>{children}</div> } })) test('cover ref empty branch in component flow', () => { render(<Component />) // 拿到img对应的ref对象,手动把current置空,模拟ref未赋值的场景 const imgEl = screen.getByRole('img') imgEl.ref.current = null // 触发onAlign回调 globalThis.testTriggerAlign(document.createElement('div')) // 后续可以加断言,验证空ref时后续逻辑没有执行 })
方案3:劫持useRef返回值,拦截current赋值
你之前mock useRef失效是因为没有拦截current属性的写入操作,可以通过属性劫持让ref的current永远保持初始null值,React挂载DOM时的赋值操作会直接被拦截:
const mockUseRef = (initialVal) => { const ref = {} Object.defineProperty(ref, 'current', { get: () => initialVal, set: () => { /* 拦截所有赋值操作,current始终为初始null */ }, configurable: true }) return ref } // 在测试文件里mock react的useRef jest.mock('react', () => ({ ...jest.requireActual('react'), useRef: mockUseRef }))
注意这个方案会污染当前测试文件里所有用到useRef的逻辑,非必要不推荐使用,很容易导致其他测试用例异常。
注意:不要尝试直接给
<img>传ref={null}来覆盖分支,React内部会对传入的ref做类型判断,如果ref不是对象或函数,会直接跳过ref挂载流程,这时候你传入组件的imgRef本身还是正常的ref对象,只是没有被React绑定到img上,和你要模拟的ref.current === null场景不完全一致。
内容的提问来源于stack exchange,提问作者putershare223

