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. 解决方案的技术正确性
你的修改方向是对的,但存在两个细节问题:
- 移除
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
相关产品推荐
相关产品推荐

