React Testing Library单元测试报错:无法在已卸载组件上找到节点
问题:React单元测试同一文件多用例报错
Unable to find node on an unmounted component 环境
- React v18.2
- Node v18.8
- 使用msw作为mock服务器
问题描述
我封装了RenderWithReactIntl函数,用来结合ReactIntl渲染组件。但在同一测试文件中运行两个测试用例时,第二个用例会抛出如下错误:
Unable to find node on an unmounted component. at findCurrentFiberUsingSlowPath (node_modules/react-dom/cjs/react-dom.development.js:4552:13) at findCurrentHostFiber (node_modules/react-dom/cjs/react-dom.development.js:4703:23) at findHostInstanceWithWarning (node_modules/react-dom/cjs/react-dom.development.js:28745:21) at Object.findDOMNode (node_modules/react-dom/cjs/react-dom.development.js:29645:12) at Transition.performEnter (node_modules/react-transition-group/cjs/Transition.js:280:71) at node_modules/react-transition-group/cjs/Transition.js:259:27
如果把测试用例拆分到不同文件,或者在测试用例中添加setTimeout,就能正常运行无报错。即使是同一测试用例重复执行也会触发该错误。
相关配置
全局测试钩子
setUpIntlConfig(); beforeAll(() => server.listen()); afterEach(() => { server.resetHandlers(); }); afterAll(() => { jest.clearAllMocks(); server.close(); cleanup(); });
国际化配置
export const setUpIntlConfig = () => { if (global.Intl) { Intl.NumberFormat = IntlPolyfill.NumberFormat; Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat; } else { global.Intl = IntlPolyfill; } }; export const RenderWithReactIntl = (component: any) => { return { ...render( <IntlProvider locale="en" messages={en}> {component} </IntlProvider> ) }; };
测试用例
test('fire get resource details with data', async () => { jest.spyOn(SGWidgets, 'getAuthorizationHeader').mockReturnValue('test-access-token'); process.env = Object.assign(process.env, { REACT_APP_DIAM_API_ENDPOINT: '' }); RenderWithReactIntl(<AllocatedAccess diamUserId={diamUserIdWithData} />); await waitForElementToBeRemoved(() => screen.getByText(/loading data.../i)); const viewResource = screen.getAllByText(/view resource/i); fireEvent.click(viewResource[0]); await waitForElementToBeRemoved(() => screen.getByText(/loading/i)); const ownerName = screen.getByText(/benedicte masson/i); expect(ownerName).toBeInTheDocument(); }); test('fire get resource details with data----2', async () => { jest.spyOn(SGWidgets, 'getAuthorizationHeader').mockReturnValue('test-access-token'); process.env = Object.assign(process.env, { REACT_APP_DIAM_API_ENDPOINT: '' }); RenderWithReactIntl(<AllocatedAccess diamUserId={diamUserIdWithData} />); await waitForElementToBeRemoved(() => screen.getByText(/loading data.../i)); const viewResource = screen.getAllByText(/view resource/i); fireEvent.click(viewResource[0]); await waitForElementToBeRemoved(() => screen.getByText(/loading/i)); const ownerName = screen.getByText(/benedicte masson/i); expect(ownerName).toBeInTheDocument(); });
请问是不是缺少某些配置导致的?
解决方案
这个问题核心是测试用例间未彻底清理组件,前一个测试的react-transition-group Transition组件还在执行异步状态更新,后一个测试已开始渲染,引发未挂载组件的DOM查找错误。以下是针对性修复方案:
1. 每个测试后强制清理组件
你当前仅在afterAll中调用cleanup(),但需在每个测试用例结束后执行清理,确保组件完全卸载:
afterEach(() => { server.resetHandlers(); // 移到afterEach,每次测试后立即清理组件 cleanup(); }); afterAll(() => { jest.clearAllMocks(); server.close(); });
2. 手动调用unmount方法
render函数会返回unmount方法,测试结束时手动调用,彻底卸载当前测试的组件:
test('fire get resource details with data', async () => { jest.spyOn(SGWidgets, 'getAuthorizationHeader').mockReturnValue('test-access-token'); process.env.REACT_APP_DIAM_API_ENDPOINT = ''; // 保存render返回结果,获取unmount方法 const { unmount } = RenderWithReactIntl(<AllocatedAccess diamUserId={diamUserIdWithData} />); await waitForElementToBeRemoved(() => screen.getByText(/loading data.../i)); const viewResource = screen.getAllByText(/view resource/i); fireEvent.click(viewResource[0]); await waitForElementToBeRemoved(() => screen.getByText(/loading/i)); const ownerName = screen.getByText(/benedicte masson/i); expect(ownerName).toBeInTheDocument(); // 测试结束手动卸载 unmount(); });
3. 禁用测试环境下的过渡动画
react-transition-group的异步过渡是报错诱因,可在测试环境中直接禁用动画:
// 组件内部根据环境判断 const isTestEnv = process.env.NODE_ENV === 'test'; // 给Transition设置0超时,跳过动画 <Transition timeout={isTestEnv ? 0 : 300}> {/* 组件内容 */} </Transition>
4. 正确修改process.env
直接用Object.assign修改会污染全局环境,测试后需恢复原变量:
test('...', async () => { const originalEndpoint = process.env.REACT_APP_DIAM_API_ENDPOINT; process.env.REACT_APP_DIAM_API_ENDPOINT = ''; // 测试逻辑 // 恢复原环境变量 process.env.REACT_APP_DIAM_API_ENDPOINT = originalEndpoint; });
5. 每个测试后重置mock
将jest.clearAllMocks()移到afterEach,避免mock残留影响后续测试:
afterEach(() => { server.resetHandlers(); cleanup(); jest.clearAllMocks(); });
内容的提问来源于stack exchange,提问作者Ranjith
相关产品推荐
相关产品推荐

