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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 08:05:35