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

React Testing Library/Jest测试ErrorBoundary HOC报错排查

ErrorBoundary高阶组件测试异常问题修复

问题描述

为自定义实现的ErrorBoundary高阶组件编写测试用例时,模拟被包裹组件抛出错误后,测试框架未将该错误识别为测试场景下的预期触发行为,直接因抛出的错误运行失败,初步排查发现错误在render执行阶段就抛出,无法正常执行后续断言逻辑。

现有ErrorBoundary HOC实现

interface StateProps {
  error: unknown;
  info: unknown;
}

interface ErrorBoundaryProps {
  createNotification(...args: any): void;
}

const connector = connect(null, {
  createNotification: createNotificationAction,
});

export const withErrorBoundary = <P extends object>(TargetComponent: React.ComponentType<P>) => {
  class ErrorBoundary extends React.Component<ErrorBoundaryProps, StateProps> {
    constructor(props: ErrorBoundaryProps) {
      super(props);
      this.state = {
        error: null,
        info: null,
      };
    }

    public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
      this.props.createNotification({
        alertType: 'error',
        message: `${TargetComponent.name} is not being rendered. Error: ${error.message}`,
      });
      this.setState({ error, info: errorInfo });
    }

    public render() {
      const { ...props } = this.props;
      if (this.state.error instanceof Error) {
        return null;
      }
      return <TargetComponent {...(props as P)} />;
    }
  }

  return connector(ErrorBoundary) as any;
};

原有问题测试用例

describe('ErrorBoundary HOC', () => {
  let store;
  let createNotification;
  let props;

  beforeEach(() => {
    store = configureStore(defaultState);
    createNotification = jest.fn();

    props = {
      createNotification,
    };

  });

  test('Renders nothing if error', async () => {
    const targetComponent = () => {
      throw new Error('Errored!');
    };
    const WrappedComponent = withErrorBoundary(targetComponent);
    const RenderedComponent = render(
      <BrowserRouter>
        <Provider store={store}>
          <WrappedComponent {...props} />
        </Provider>
      </BrowserRouter>
    );

    await waitFor(() => expect(() => WrappedComponent.toThrow()));
    expect(RenderedComponent.container.hasChildNodes()).toBeFalsy();
    await waitFor(() => expect(createNotification).toHaveBeenCalled());
  });
});

问题根源

  • React 16及以上版本中,错误边界捕获子组件错误后,错误依然会冒泡到全局环境,Jest + JSDOM默认会将所有未被显式标记为预期的全局错误判定为测试失败,并非错误边界没有捕获错误,是测试环境默认行为导致测试提前中断。
  • 原有测试中await waitFor(() => expect(() => WrappedComponent.toThrow()));写法完全无效:WrappedComponent是经过connect包裹的组件对象,不是可直接执行的渲染函数,这段断言既不会捕获渲染阶段抛出的错误,也无法将错误标记为测试预期行为。
  • 被connect包裹的组件会优先使用绑定到dispatch的action creator,手动通过props传入的createNotificationmock函数会被覆盖,根本不会被组件内部调用,后续通知断言必然失败。

修复方案

  1. 测试渲染抛出错误的组件前,临时屏蔽React抛出的预期错误日志,避免测试框架将预期错误判定为用例失败,测试执行完成后恢复原有全局日志逻辑,避免影响其他用例。
  2. 修正mock逻辑:针对connect绑定的createNotification action,通过mock store.dispatch的方式验证action触发,不要尝试通过props传入mock函数覆盖connect绑定的逻辑。
  3. 删除无效的toThrow断言,等待错误边界完成状态更新、action调用后,直接验证渲染结果和通知触发逻辑即可。

可选优化:如果不需要测试connect绑定逻辑,可以将HOC内部的ErrorBoundary类单独导出,测试时直接传入mock的createNotification prop,不需要套Provider、mock store,测试逻辑会更简洁。

修复后的测试用例

describe('ErrorBoundary HOC', () => {
  let store;
  let createNotificationMock;
  // 保存原有全局错误日志方法
  const originalConsoleError = console.error;

  beforeEach(() => {
    store = configureStore(defaultState);
    createNotificationMock = jest.fn();
    // mock store.dispatch,捕获createNotification action调用
    store.dispatch = jest.fn((action) => {
      if (action.type === createNotificationAction.type) {
        createNotificationMock(action.payload);
      }
      return action;
    });
    // 临时屏蔽React抛出的预期错误日志
    console.error = jest.fn();
  });

  afterEach(() => {
    // 恢复原有全局日志方法
    console.error = originalConsoleError;
  });

  test('子组件抛出错误时不渲染内容、触发错误通知', async () => {
    const ErroredComponent = () => {
      throw new Error('Errored!');
    };
    const WrappedComponent = withErrorBoundary(ErroredComponent);
    
    render(
      <BrowserRouter>
        <Provider store={store}>
          <WrappedComponent />
        </Provider>
      </BrowserRouter>
    );

    // 等待错误边界完成setState和通知调用
    await waitFor(() => {
      expect(createNotificationMock).toHaveBeenCalledWith({
        alertType: 'error',
        message: expect.stringContaining('Errored!')
      });
    });

    // 验证错误状态下组件返回null,无渲染内容
    expect(document.body.firstChild?.firstChild).toBeNull();
  });
});

内容的提问来源于stack exchange,提问作者Samira Arabgol

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 18:57:49