React Native单元测试中user.press触发组件卸载报错的解决方法
user.press触发"Unable to find node on an unmounted component"错误 当使用user.press测试点击后会卸载自身的组件时,出现如下错误:
Unable to find node on an unmounted component.
at findCurrentFiberUsingSlowPath (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:574:13)
at ReactTestInstance._currentFiber (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:18325:17)
at ReactTestInstance.get (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:18397:19)
at props (node_modules/@testing-library/react-native/src/user-event/utils/dispatch-event.ts:25:26)
at getEventHandler (node_modules/@testing-library/react-native/src/user-event/utils/dispatch-event.ts:12:19)
at emitTextPressEvents (node_modules/@testing-library/react-native/src/user-event/press/press.ts:125:16)
at basePress (node_modules/@testing-library/react-native/src/user-event/press/press.ts:45:5)
at Object.press (node_modules/@testing-library/react-native/src/user-event/press/press.ts:17:3)
at wrapAsync (node_modules/@testing-library/react-native/src/helpers/wrap-async.ts:19:22)
问题核心是:点击操作触发状态更新导致按钮从元素树卸载,但user.press会模拟完整的用户交互流程(按下、抬起等),后续步骤会尝试访问已卸载的节点,引发报错。
复现代码
React组件
export function TestComponent(): JSX.Element { const [isVisible, setIsVisible] = useState(true); return <View>{isVisible && <Text onPress={() => setIsVisible(false)}>Press_me</Text>}</View>; }
测试文件(报错版本)
import { render, screen, userEvent } from '@testing-library/react-native'; import { TestComponent } from '../App'; import { it } from '@jest/globals'; it('fails successfully', async () => { render(<TestComponent />); const user = userEvent.setup(); await user.press(screen.getByText('Press_me')); });
已知fireEvent.press可规避报错,但希望保留user.press以保证交互模拟的一致性和真实性。
以下几种方式可让user.press无报错运行:
1. 提前缓存元素引用
在执行user.press前,先获取并保存元素的引用,避免交互过程中重复查询已卸载的节点:
import { render, screen, userEvent } from '@testing-library/react-native'; import { TestComponent } from '../App'; import { it } from '@jest/globals'; it('works with cached element', async () => { render(<TestComponent />); // 提前获取元素引用 const pressButton = screen.getByText('Press_me'); const user = userEvent.setup(); await user.press(pressButton); });
2. 结合waitFor验证卸载结果
将测试逻辑聚焦于交互后的状态变化,用waitFor等待DOM更新完成,同时避免user.press后续步骤访问无效节点:
import { render, screen, userEvent, waitFor } from '@testing-library/react-native'; import { TestComponent } from '../App'; import { it, expect } from '@jest/globals'; it('verifies button is removed after press', async () => { render(<TestComponent />); const user = userEvent.setup(); await user.press(screen.getByText('Press_me')); // 验证按钮已卸载 await waitFor(() => { expect(screen.queryByText('Press_me')).toBeNull(); }); });
3. 延迟组件卸载逻辑(可选)
若允许修改组件,可将卸载逻辑延迟到交互流程完成后执行,给user.press的内部步骤留出完成时间:
export function TestComponent(): JSX.Element { const [isVisible, setIsVisible] = useState(true); const handlePress = () => { // 用微任务延迟卸载 Promise.resolve().then(() => setIsVisible(false)); }; return <View>{isVisible && <Text onPress={handlePress}>Press_me</Text>}</View>; }
内容的提问来源于stack exchange,提问作者Nowhereman

