@testing-library/react unmount失效,useEffect清理函数未执行求助
@testing-library/react unmount未触发useEffect清理函数的问题排查与解决
我在使用@testing-library/react的unmount方法时遇到了失效问题——调用unmount后,组件中useEffect的返回清理函数并未执行(控制台未打印unsubscribing日志),导致测试中emitter.off的调用次数不符合预期。
组件代码
React.useEffect(() => { emitter.on(MY_EVENT, callback); return () => { console.log('unsubscribing'); // 未打印该日志 emitter.off(MY_EVENT, callback); }; }, []);
测试代码
it.only('should add and remove event listeners', () => { const { unmount } = render(<Component {...defaultProps} />); expect(emitter.on).toHaveBeenCalledTimes(1); expect(observers[MY_EVENT]).toBeDefined(); unmount(); expect(emitter.off).toHaveBeenCalledTimes(1); expect(observers[MY_EVENT]).toBeUndefined(); });
测试报错信息
expect(jest.fn()).toHaveBeenCalledTimes(expected) Expected number of calls: 1 Received number of calls: 0 23 | 24 | unmount(); > 25 | expect(emitter.off).toHaveBeenCalledTimes(1); | ^ 26 | expect(observers[MY_EVENT]).toBeUndefined();
依赖版本
"react": "17.0.2", "react-dom": "17.0.2", "@testing-library/jest-dom": "5.15.0", "@testing-library/react": "12.1.2"
排查与解决方案
1. 固定回调函数的引用一致性
如果callback是组件内部定义的普通函数,每次组件渲染都会生成新的引用。即使useEffect依赖为空数组,若组件存在意外重渲染,会导致emitter.on绑定的回调与emitter.off解绑的回调不是同一个引用,看似清理函数未执行(实际解绑失败)。用useCallback固定引用:
const callback = React.useCallback(() => { // 你的回调逻辑 }, []); React.useEffect(() => { emitter.on(MY_EVENT, callback); return () => { console.log('unsubscribing'); emitter.off(MY_EVENT, callback); }; }, [callback]);
2. 手动调用react-dom的卸载API
绕过@testing-library/react的unmount函数,直接用react-dom的unmountComponentAtNode手动卸载组件,验证是否能触发清理:
import { unmountComponentAtNode } from 'react-dom'; it.only('should add and remove event listeners', () => { const container = document.createElement('div'); document.body.appendChild(container); render(<Component {...defaultProps} />, { container }); expect(emitter.on).toHaveBeenCalledTimes(1); expect(observers[MY_EVENT]).toBeDefined(); unmountComponentAtNode(container); expect(emitter.off).toHaveBeenCalledTimes(1); expect(observers[MY_EVENT]).toBeUndefined(); });
3. 排查组件卸载时的隐藏错误
组件卸载过程中如果抛出异常,会中断清理函数的执行。可以在测试中监听console.error,排查是否有未捕获的错误:
beforeEach(() => { jest.spyOn(console, 'error').mockImplementation(() => {}); }); afterEach(() => { console.error.mockRestore(); }); it.only('should add and remove event listeners', () => { const { unmount } = render(<Component {...defaultProps} />); expect(emitter.on).toHaveBeenCalledTimes(1); unmount(); expect(console.error).not.toHaveBeenCalled(); expect(emitter.off).toHaveBeenCalledTimes(1); expect(observers[MY_EVENT]).toBeUndefined(); });
4. 验证emitter的mock逻辑
如果emitter是用jest mock的,确保on和off的mock没有拦截或篡改正常调用:
jest.mock('./path-to-emitter', () => ({ on: jest.fn(), off: jest.fn(), // 其他需要mock的属性 }));
内容的提问来源于stack exchange,提问作者Bhushan Laware
相关产品推荐
相关产品推荐

