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

React PersistLogin组件测试卡顿问题及后续优化咨询

问题描述

初始测试代码

describe('App', () => {
  it('renders headline', async () => {
    render(<App />, { wrapper: BrowserRouter });
    expect(screen.getByText('Welcome to Mentor Blog')).toBeInTheDocument();
  });
});

PersistLogin组件代码

export const PersistLogin = () => {
  const [isLoading, setIsLoading] = useState(true);
  const refresh = useRefreshToken();
  const { user } = useApplicationState();

  useEffect(() => {
    let isMounted = true;

    const verifyRefreshToken = async () => {
      try {
        await refresh();
      } catch (error) {
        console.error(error);
      } finally {
        isMounted && setIsLoading(false);
      }
    };

    !user?.accessToken ? verifyRefreshToken() : setIsLoading(false);

    return () => isMounted = false;
  }, []);

  return (
    <>
      {isLoading ? <p>Loading...</p> : <Outlet />}
    </>
  );
};

测试失败情况

测试执行后,PersistLogin组件停留在加载状态,控制台输出DOM片段:

<divclass="menuItem">
          Mentor Blog
        </div>
        <div
          class="menuItem"
        >
          Log in
        </div>
      </div>
      <p>
        Loading...
      </p>
    </div>

修改后的测试代码(测试通过)

describe('App', () => {
  it('renders headline', async () => {
    render(<App />);
    const element = await screen.findByText('Welcome to Mentor Blog');

    expect(element).toBeInTheDocument();
  });
});

根据Testing Library文档,getBy方法不支持异步操作,但修改后出现控制台错误信息,现咨询:

  1. 该解决方案在技术层面是否正确?
  2. 如何处理此控制台错误?

解答

1. 解决方案的技术正确性

你的修改方向是对的,但存在两个细节问题:

  • 移除BrowserRouter包装器不合理:如果App组件实际运行时依赖BrowserRouter,测试环境移除它会导致环境不一致,后续可能引发路由相关测试问题,应该保留{ wrapper: BrowserRouter }参数。
  • 使用findByText的合理性:findBy系列方法是Testing Library专为异步场景设计的(本质是getBy+waitFor的封装),适合等待组件异步状态更新后的DOM渲染。因为PersistLogin会先显示加载状态,等refresh异步操作完成后才渲染Outlet(包含目标标题),所以用await screen.findByText()替代同步的getByText完全符合规范。

正确的测试代码应该是:

describe('App', () => {
  it('renders headline', async () => {
    render(<App />, { wrapper: BrowserRouter });
    const element = await screen.findByText('Welcome to Mentor Blog');
    expect(element).toBeInTheDocument();
  });
});

2. 处理控制台错误

控制台错误大概率来自refresh函数调用失败(测试环境无有效刷新令牌,或useRefreshToken钩子的依赖服务未被模拟),可通过以下方式解决:

方式一:模拟useRefreshToken钩子

在测试中模拟useRefreshToken,让它返回成功的空Promise,避免实际请求失败:

import { useRefreshToken } from '../path-to-your-hooks';

jest.mock('../path-to-your-hooks');

describe('App', () => {
  beforeEach(() => {
    (useRefreshToken as jest.Mock).mockResolvedValueOnce();
  });

  it('renders headline', async () => {
    render(<App />, { wrapper: BrowserRouter });
    const element = await screen.findByText('Welcome to Mentor Blog');
    expect(element).toBeInTheDocument();
  });
});

方式二:模拟useApplicationState返回已登录用户

如果测试无需验证刷新令牌逻辑,直接让useApplicationState返回带accessToken的用户对象,PersistLogin会跳过refresh调用,立即渲染Outlet:

import { useApplicationState } from '../path-to-your-state';

jest.mock('../path-to-your-state');

describe('App', () => {
  beforeEach(() => {
    (useApplicationState as jest.Mock).mockReturnValue({
      user: { accessToken: 'mock-token' }
    });
  });

  it('renders headline', async () => {
    render(<App />, { wrapper: BrowserRouter });
    // 无需等待异步操作,直接用getByText
    expect(screen.getByText('Welcome to Mentor Blog')).toBeInTheDocument();
  });
});

方式三:临时抑制控制台错误输出

如果只是不想看到错误日志(不推荐作为长期方案),可在测试中临时覆盖console.error:

describe('App', () => {
  beforeEach(() => {
    jest.spyOn(console, 'error').mockImplementation(() => {});
  });

  afterEach(() => {
    (console.error as jest.Mock).mockRestore();
  });

  it('renders headline', async () => {
    render(<App />, { wrapper: BrowserRouter });
    const element = await screen.findByText('Welcome to Mentor Blog');
    expect(element).toBeInTheDocument();
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 15:03:28