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

测试ErrorBoundary错误恢复遇问题:点击重试后alert仍存在

问题根源

你的测试失败核心原因是:点击"Try again?"按钮后,ErrorBoundary将hasError重置为false,会重新渲染子组件<ThrowError />——但这个组件每次渲染都会无条件抛出错误,导致ErrorBoundary立刻再次捕获错误,hasError被重新设为true,所以alert元素始终存在。

解决方案

方案1:修改测试用例,让子组件可控制是否报错

给测试用的ThrowError组件添加状态开关,确保点击重试前,子组件已经不再抛出错误:

it("recovers from error boundary", async () => {
  const user = userEvent.setup();
  const [shouldThrow, setShouldThrow] = useState(true);
  
  const ThrowError = () => {
    if (shouldThrow) {
      throw new Error("Test");
    }
    return <div data-testid="success-content">恢复成功</div>;
  };

  render(
    <ErrorBoundary>
      <ThrowError />
    </ErrorBoundary>
  );

  // 验证错误提示出现
  expect(screen.getByRole("alert")).toBeInTheDocument();

  // 先更新状态让组件停止报错,再点击重试
  await act(() => {
    setShouldThrow(false);
  });
  await user.click(screen.getByRole("button", { name: "Try again?" }));

  // 验证错误提示消失,成功内容出现
  expect(screen.queryByRole("alert")).not.toBeInTheDocument();
  expect(screen.getByTestId("success-content")).toBeInTheDocument();
});

方案2:修改ErrorBoundary,通过key强制子组件重新挂载

如果你的实际业务中子组件是"可恢复"的(比如初始加载失败,重新挂载能正常渲染),可以给子组件添加动态key,点击重试时更新key触发重新挂载:

class ErrorBoundary extends Component<ErrorBoundryProps, ErrorBoundryState> {
  state = { hasError: false, resetKey: 0 };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: Error, info: ErrorInfo) {
    // 错误日志逻辑
  }

  handleReset = () => {
    this.setState(prev => ({ 
      hasError: false,
      resetKey: prev.resetKey + 1
    }));
  };

  render() {
    if (this.state.hasError) {
      return (
        <div className="app-error-boundry" role="alert">
          <div className="app-error-boundry__content">
            <h1 className="app-error-boundry__title">Something went wrong.</h1>
            <button
              type="button"
              className="app-error-boundry__btn btn"
              onClick={this.handleReset}
            >
              Try again?
            </button>
          </div>
        </div>
      );
    }

    // 通过key强制子组件重新挂载
    return React.cloneElement(this.props.children, {
      key: this.state.resetKey
    });
  }
}

此时原测试用例可以正常通过,因为子组件会被重新初始化,若它的错误是一次性的(比如仅首次渲染失败),就能正常恢复。

方案3:用Mock组件验证重置逻辑

如果只想验证ErrorBoundary的状态重置逻辑,可以用jest mock控制子组件的报错行为:

it("resets error state on button click", async () => {
  const user = userEvent.setup();
  const MockChild = jest.fn(() => <div>正常内容</div>);
  
  render(
    <ErrorBoundary>
      <MockChild />
    </ErrorBoundary>
  );

  // 模拟子组件首次渲染报错
  MockChild.mockImplementationOnce(() => { throw new Error("Test"); });
  await act(() => {
    // 触发重新渲染
    render(
      <ErrorBoundary>
        <MockChild />
      </ErrorBoundary>
    );
  });

  expect(screen.getByRole("alert")).toBeInTheDocument();

  // 恢复子组件正常渲染
  MockChild.mockImplementation(() => <div>正常内容</div>);
  await user.click(screen.getByRole("button", { name: "Try again?" }));

  expect(screen.queryByRole("alert")).not.toBeInTheDocument();
  expect(screen.getByText("正常内容")).toBeInTheDocument();
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 09:41:03