测试useAuthentication hook时Vitest模块模拟报错,求解决方案
我在验证useAuthentication Hook里的navigate方法是否以'/'为参数被调用时,遇到了如下错误:
Error: [vitest] There was an error when mocking a module. If you are using "vi.mock" factory, make sure there are no top level variables inside, since this call is hoisted to top of the file.
相关代码
useAuthentication.ts
import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { getCookie } from '../utility/cookieUtils'; import authService from '../services/authService'; type UseAuthenticationType = { isAuthenticated: boolean; login: (username: string, password: string) => Promise<void>; userName: string | null; isLoginError: boolean; }; const useAuthentication = (): UseAuthenticationType => { const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false); const [userName, setUserName] = useState<string | null>(null); const [isLoginError, setIsLoginError] = useState<boolean>(false); const navigate = useNavigate(); useEffect(() => { const cookie = getCookie('jwtToken'); const user = localStorage.getItem('username'); if (user !== null) { setUserName(user); } if (cookie.length) { setIsAuthenticated(true); } else { navigate('/'); localStorage.removeItem('username'); } }, [navigate]); const handleLogin = async (username: string, password: string) => { try { const data = await authService.login(username, password); setIsAuthenticated(true); setUserName(data.username); navigate('/order-entry'); localStorage.setItem('username', data.username); } catch (error) { // eslint-disable-next-line no-console console.error('Error signing in:', error); setIsLoginError(true); setIsAuthenticated(false); } }; return { isAuthenticated, login: handleLogin, userName, isLoginError }; }; export default useAuthentication;
useAuthentication.test.ts
import { describe, expect, it, vi } from 'vitest'; import { renderHook } from '@testing-library/react'; import useAuthentication from './useAuthentication'; // Mock the cookieUtils module vi.mock('../utility/cookieUtils', () => ({ getCookie: vi.fn().mockReturnValue(''), // Mocking getCookie to return empty string })); describe('useAuthentication hook', () => { it('should call navigate with "/" when cookie is not present', () => { // Mock the navigate function const mockNavigate = vi.fn(); // Mock useNavigate hook vi.mock('react-router-dom', async () => { const mod = await vi.importActual('react-router-dom'); return { ...mod, useNavigate: vi.fn().mockReturnValue(mockNavigate), }; }); // Render the hook renderHook(() => useAuthentication()); // Check that navigate was called with "/" expect(mockNavigate).toHaveBeenCalledWith('/'); }); });
问题原因与修复方案
原因
Vitest的vi.mock会被强制提升到文件顶部执行,而你在测试用例内部定义的mockNavigate变量,在vi.mock被提升后还未初始化,导致mock工厂函数无法访问该变量,从而抛出错误。
修复方案1:将mock移到文件顶部
把react-router-dom的mock提前到文件顶部,确保变量在mock执行前已声明:
import { describe, expect, it, vi } from 'vitest'; import { renderHook } from '@testing-library/react'; import useAuthentication from './useAuthentication'; // Mock the cookieUtils module vi.mock('../utility/cookieUtils', () => ({ getCookie: vi.fn().mockReturnValue(''), })); // 提前定义mockNavigate并完成react-router-dom的mock const mockNavigate = vi.fn(); vi.mock('react-router-dom', async () => { const mod = await vi.importActual('react-router-dom'); return { ...mod, useNavigate: vi.fn().mockReturnValue(mockNavigate), }; }); describe('useAuthentication hook', () => { it('should call navigate with "/" when cookie is not present', () => { // 重置mock调用记录,避免其他测试干扰 mockNavigate.mockClear(); renderHook(() => useAuthentication()); expect(mockNavigate).toHaveBeenCalledWith('/'); }); });
修复方案2:使用vi.spyOn替代vi.mock
用vi.spyOn单独mockuseNavigate方法,避开mock提升的作用域问题:
import { describe, expect, it, vi } from 'vitest'; import { renderHook } from '@testing-library/react'; import * as reactRouterDom from 'react-router-dom'; import useAuthentication from './useAuthentication'; // Mock the cookieUtils module vi.mock('../utility/cookieUtils', () => ({ getCookie: vi.fn().mockReturnValue(''), })); describe('useAuthentication hook', () => { it('should call navigate with "/" when cookie is not present', () => { const mockNavigate = vi.fn(); // 直接spyOn并mock useNavigate的返回值 vi.spyOn(reactRouterDom, 'useNavigate').mockReturnValue(mockNavigate); renderHook(() => useAuthentication()); expect(mockNavigate).toHaveBeenCalledWith('/'); }); });
内容的提问来源于stack exchange,提问作者Ankur Marwaha
相关产品推荐
相关产品推荐

