You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 02:35:19