React 18+React Hook Form RTL测试中unsubscribe未定义错误排查
问题:React 18.3.1 + React Hook Form 7.54.1 RTL测试报错:subscription.unsubscribe()不存在
错误信息
42 | setSomeState(newValue); // changed to dummy for now 43 | }) > 44 | return () => subscription.unsubscribe() | ^ 45 | }, [watch]) 46 | return { 47 | nameCount, at unsubscribe (src/pages/TestFileName.tsx:44:29) at safelyCallDestroy (node_modules/react-dom/cjs/react-dom.development.js:3922:844) at commitHookEffectListUnmount (node_modules/react-dom/cjs/react-dom.development.js:3929:331) at commitPassiveUnmountInsideDeletedTreeOnFiber (node_modules/react-dom/cjs/react-dom.development.js:4161:525) at commitPassiveUnmountEffectsInsideOfDeletedTree_begin (node_modules/react-dom/cjs/react-dom.development.js:4156:24) at commitPassiveUnmountEffects_begin (node_modules/react-dom/cjs/react-dom.development.js:4143:2054) at commitPassiveUnmountEffects (node_modules/react-dom/cjs/react-dom.development.js:4143:1714) at flushPassiveEffectsImpl (node_modules/react-dom/cjs/react-dom.development.js:4640:360) at flushPassiveEffects (node_modules/react-dom/cjs/react-dom.development.js:4635:371) at commitRootImpl (node_modules/react-dom/cjs/react-dom.development.js:4625:82) at commitRoot (node_modules/react-dom/cjs/react-dom.development.js:4549:209) at performSyncWorkOnRoot (node_modules/react-dom/cjs/react-dom.development.js:4444:97) at flushSyncCallbacks (node_modules/react-dom/cjs/react-dom.development.js:2155:108) at flushSync (node_modules/react-dom/cjs/react-dom.development.js:4457:66) at ReactDOMRoot.Object.<anonymous>.ReactDOMHydrationRoot.unmount.ReactDOMRoot.unmount [as unmount] (node_modules/react-dom/cjs/react-dom.development.js:4917:1902) at Object.unmount (node_modules/@testing-library/react/dist/pure.js:178:12) at node_modules/@testing-library/react/dist/pure.js:309:12 at node_modules/@testing-library/react/dist/act-compat.js:71:24 at act (node_modules/react/cjs/react.development.js:2194:20) at node_modules/@testing-library/react/dist/act-compat.js:70:25 at node_modules/@testing-library/react/dist/pure.js:308:28 at node_modules/@testing-library/react/dist/act-compat.js:70:25 at node_modules/@testing-library/react/dist/pure.js:308:28 at node_modules/@testing-library/react/dist/pure.js:308:28 at Array.forEach (<anonymous>) at cleanup (node_modules/@testing-library/react/dist/pure.js:304:22) at Object.<anonymous> (src/pages/TestFileName.test.tsx:78:10)
相关代码片段
TestFileName.tsx
const [nameCount, setNameCount] = useState(someStateData?.name?.length || 0) useEffect(() => { const subscription = watch((value) => { setNameCount(value.name.length); }) return () => subscription.unsubscribe(); // 测试中报错的行 }, [watch]) return { nameCount } }
ParentContainer.tsx
const { register, handleSubmit,....// other methods } = useForm({ defaultValues: { firstName: someDefaultValue, },... // other attributes }); return ( <> <TestFileName register={register} watch={watch} setValue={setValue} setFocus={setFocus} /> </> )
测试用例默认props
const defaultProps = { register: jest.fn(), watch: jest.fn(), setError: jest.fn() };
问题原因
测试用例里的watch是一个空的jest mock函数,而真实的React Hook Form watch函数调用后会返回一个带有unsubscribe方法的订阅对象。你的mockwatch没有返回这个对象,导致组件卸载执行清理函数时,subscription要么是undefined,要么没有unsubscribe方法,从而抛出错误。
解决方案
调整测试用例中的watch mock,让它返回包含unsubscribe方法的对象:
const defaultProps = { register: jest.fn(), watch: jest.fn(() => ({ unsubscribe: jest.fn() })), setError: jest.fn() };
如果需要模拟watch的回调逻辑(比如测试状态更新),可以进一步完善mock:
const watchMock = jest.fn((callback) => { // 可在这里触发回调模拟表单值变化 // callback({ name: '测试名称' }); return { unsubscribe: jest.fn() }; }); const defaultProps = { register: jest.fn(), watch: watchMock, setError: jest.fn() };
内容的提问来源于stack exchange,提问作者Nikhil Singh Bhadoriya
相关产品推荐
相关产品推荐

