Jest断言登录组件错误提示3秒后消失失败的问题
登录组件错误提示自动隐藏的Jest测试问题解决
问题场景
测试登录组件请求失败后的错误状态:请求失败时显示错误提示,3秒后通过useEffect更新状态隐藏错误。但Jest测试中执行断言await waitFor(() => expect(getByRole('alert')).not.toBeInTheDocument());时报错,提示错误元素仍存在。尝试使用Jest的fake timers模拟setTimeout未解决问题。
测试代码
import { render } from "@testing-library/react"; import { waitFor } from "@testing-library/dom"; import userEvent from '@testing-library/user-event'; test('Throws an error for wrong credentials', async () => { const { getByText, getByRole, getByLabelText, } = render(<Login />); expect(getByText(/Admin login/i)).toBeInTheDocument(); const emailField = getByLabelText('Admin email'); const passwordField = getByLabelText('Password'); const button = getByText('Sign in'); userEvent.type(emailField, 'wrongemail@gmail.com'); userEvent.type(passwordField, 'Wrong666!!!'); userEvent.click(button); expect(getByRole('progressbar')).toBeInTheDocument(); jest.useFakeTimers(); jest.spyOn(window, 'setTimeout'); await waitFor(() => expect(getByRole('alert')).toHaveTextContent('These credentials do not match our records.')); await waitFor(() => expect(getByRole('alert')).not.toBeInTheDocument()); });
报错信息
expected document not to contain element, found
<div class="sc-bczRLJ iXcWnZ text " color="gray-primary" role="alert" title="">These credentials do not match our records.</div>instead
组件代码
import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import { Text } from 'core/components/text'; import { login } from 'reducers/auth'; export const Login = () => { const navigate = useNavigate(); const dispatch = useDispatch<SharedDispFn>(); const [loginError, setLoginError] = useState(''); const onSignIn = async () => { const loginResponse = await dispatch(login(loginData)); if (!loginResponse.status) { setLoginError(loginResponse.message); } else { navigate('/dashboard'); } }; useEffect(() => { const timer = setTimeout(() => { setLoginError(''); }, 3000); return () => clearTimeout(timer); }, [loginError]); return ( <Form checkFormValidation={setFormIsValid} onSubmit={onSignIn}> {loginError && ( <Text color="gray-primary" role="alert" children={loginError} /> )} // Rest of the code ... </Form> ); };
解决方法
1. 提前启用Fake Timers
问题根源在于:原测试代码在点击按钮后才启用fake timers,此时组件因loginError变化创建的setTimeout已经是真实定时器,fake timers无法接管。必须在渲染组件前启用fake timers,确保所有定时器都被Jest控制。
2. 手动推进时间并等待组件更新
启用fake timers后,调用jest.advanceTimersByTime(3000)模拟3秒时间流逝,再用waitFor等待组件完成状态更新和重新渲染。
3. 测试后清理Fake Timers
测试结束后恢复真实定时器,避免影响其他测试用例。
修改后的测试代码
import { render } from "@testing-library/react"; import { waitFor } from "@testing-library/dom"; import userEvent from '@testing-library/user-event'; test('Throws an error for wrong credentials', async () => { // 提前启用fake timers jest.useFakeTimers(); const { getByText, getByRole, getByLabelText, } = render(<Login />); expect(getByText(/Admin login/i)).toBeInTheDocument(); const emailField = getByLabelText('Admin email'); const passwordField = getByLabelText('Password'); const button = getByText('Sign in'); userEvent.type(emailField, 'wrongemail@gmail.com'); userEvent.type(passwordField, 'Wrong666!!!'); userEvent.click(button); expect(getByRole('progressbar')).toBeInTheDocument(); // 等待错误提示出现 await waitFor(() => expect(getByRole('alert')).toHaveTextContent('These credentials do not match our records.') ); // 模拟3秒时间流逝 jest.advanceTimersByTime(3000); // 等待组件更新,错误提示消失 await waitFor(() => expect(getByRole('alert')).not.toBeInTheDocument() ); // 恢复真实定时器 jest.useRealTimers(); });
补充说明
- 组件中的
useEffect依赖loginError,每次loginError变化都会创建新的定时器,提前启用fake timers能确保这个定时器被Jest捕获。 - 使用
jest.advanceTimersByTime而非jest.runAllTimers,可以精准模拟所需的时间,避免触发其他不必要的定时器。
内容的提问来源于stack exchange,提问作者Norayr Ghukasyan
相关产品推荐
相关产品推荐

