使用MUI Popover时出现React act警告的问题求助
解决React测试中TextField+Popover的act警告问题
测试包含TextField和Popover的React组件时,始终无法避免React的act警告。该组件由父组件控制Popover的显示时机与内容,相关代码及警告如下:
组件代码
const PopoverContainer = (props: TextFieldWithPopoverProps) => { const [anchorEl, setAnchorEl] = React.useState(null); const anchorRef = React.useRef(null); React.useEffect(() => { setAnchorEl(anchorRef.current); }, [anchorRef]); return ( <> <TextField type="text" ref={anchorRef}/> <Popover id={props.popperId} open={props.open} sx={{ zIndex: 1001 }} anchorEl={anchorEl}> <Paper> {props.renderedChild} </Paper> </Popover> </> ); };
测试代码
test('should open popover', async () => { const { rerender } = render(<PopoverContainer open={false} popperId="popper-id" renderedChild={renderInnerPopover()} />); expect(screen.queryByText('Hello World!')).toBe(null); await userEvent.type(await screen.findByRole('textbox'), 'HELLO'); rerender(<PopoverContainer open={true} popperId="popper-id" renderedChild={renderInnerPopover()} />); expect(await screen.findByText('Hello World!')); }); const renderInnerPopover = () => { return ( <div> Hello World! </div> ); };
出现的警告
Warning: An update to ForwardRef(FormControl) inside a test was not wrapped in act(...)
Warning: An update to ForwardRef(Popover) inside a test was not wrapped in act(...)
警告始终出现在rerender调用时,尝试用act()包裹rerender和type事件无效,移除TextField则警告消失,但不符合组件设计需求。
有效解决方法
1. 移除不必要的状态转换,直接使用ref.current
原组件中通过useEffect将ref.current存入state是多余操作,会触发额外组件更新,这是导致act警告的核心原因之一。直接将ref.current传给Popover的anchorEl即可:
const PopoverContainer = (props: TextFieldWithPopoverProps) => { const anchorRef = React.useRef(null); return ( <> <TextField type="text" ref={anchorRef}/> <Popover id={props.popperId} open={props.open} sx={{ zIndex: 1001 }} anchorEl={anchorRef.current}> <Paper> {props.renderedChild} </Paper> </Popover> </> ); };
2. 优化测试代码,确保所有更新被正确等待
使用waitFor包裹断言,确保等待Popover内部所有异步更新完成,避免测试在组件未完全渲染时执行断言:
import { waitFor } from '@testing-library/react'; test('should open popover', async () => { const { rerender } = render(<PopoverContainer open={false} popperId="popper-id" renderedChild={renderInnerPopover()} />); expect(screen.queryByText('Hello World!')).toBeNull(); // 组件渲染后textbox已存在,无需await await userEvent.type(screen.getByRole('textbox'), 'HELLO'); rerender(<PopoverContainer open={true} popperId="popper-id" renderedChild={renderInnerPopover()} />); // 等待Popover完全渲染完成 await waitFor(() => { expect(screen.getByText('Hello World!')).toBeInTheDocument(); }); }); const renderInnerPopover = () => { return ( <div> Hello World! </div> ); };
3. 禁用测试环境中的组件动画
Material-UI的Popover、TextField等组件内部可能包含动画逻辑,这些异步动画会触发额外状态更新,导致act警告。在测试全局配置中禁用动画:
// setupTests.ts import '@testing-library/jest-dom'; import { disableAnimations } from '@testing-library/react'; beforeAll(() => { disableAnimations(); });
内容的提问来源于stack exchange,提问作者Soul3lade
相关产品推荐
相关产品推荐

