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

测试useAuthentication hook时Vitest模块模拟报错,求解决方案

解决Vitest中Mock模块错误:验证useAuthentication Hook的navigate调用

我在验证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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 02:43:24