测试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
相关产品推荐
相关产品推荐

