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

Jest测试中自定义Hook的mock函数无法在测试块外设置Promise实现

问题描述

尝试Mock自定义Hook useAuth 返回的login函数(该函数返回Promise)时遇到问题:如果在测试块内使用mockImplementation设置函数实现,功能正常;但如果在测试块外设置,调用login时会报错“cannot read then of undefined”。

附带代码:

const mockedLogin = jest.fn(() => Promise.resolve()); // 在这里设置实现无效:报错“cannot read then of undefined”

jest.mock("../../hooks/useAuth", () => {
  return () => ({
    login: mockedLogin,
  });
});

test("submitting the form calls onSubmit with username and password", async () => {
  mockedLogin.mockImplementation(() => Promise.resolve()); // 在这里设置正常工作
  render(<Login />);
  const username = "name";
  const password = "i need no password";

  await userEvent.type(screen.getByLabelText(/username/i), username);
  await userEvent.type(screen.getByLabelText(/password/i), password);
  await userEvent.click(screen.getByRole("button", { name: /submit/i }));

  expect(mockedLogin).toHaveBeenCalledWith({
    username,
    password,
  });
  expect(mockedLogin).toHaveBeenCalledTimes(1);
});

原因分析

Jest会自动将jest.mock()调用提升到当前模块代码的最顶部执行,优先级高于模块内所有变量的声明和赋值。这意味着当jest.mock的工厂函数运行时,你在后面声明的mockedLogin变量还没有被赋值:

  • 如果用const/let声明mockedLogin,此时变量处于暂时性死区,直接引用会抛出ReferenceError;
  • 如果用var声明,变量会被初始化为undefined,导致组件拿到的login是undefined,调用时自然会报错。

而在测试块内设置mockImplementation时,mockedLogin已经被正确赋值为jest.fn实例,组件也已经获取到了这个函数引用,所以修改实现会生效。

解决方案

方案1:在mock工厂函数内部定义带实现的mock函数

这种方式彻底避免了变量提升的问题,同时通过外部变量保留对mock函数的引用,方便在测试中做断言:

// 声明一个全局可访问的变量存储mock函数
let mockedLogin;

jest.mock("../../hooks/useAuth", () => {
  // 在工厂函数内直接创建带实现的mock函数
  mockedLogin = jest.fn(() => Promise.resolve());
  return () => ({
    login: mockedLogin,
  });
});

test("submitting the form calls onSubmit with username and password", async () => {
  render(<Login />);
  const username = "name";
  const password = "i need no password";

  await userEvent.type(screen.getByLabelText(/username/i), username);
  await userEvent.type(screen.getByLabelText(/password/i), password);
  await userEvent.click(screen.getByRole("button", { name: /submit/i }));

  expect(mockedLogin).toHaveBeenCalledWith({
    username,
    password,
  });
  expect(mockedLogin).toHaveBeenCalledTimes(1);
});

方案2:直接在mock工厂函数内定义实现,通过导入获取mock函数

如果不需要在模块全局引用mock函数,也可以直接在工厂函数内定义,然后通过导入mock后的Hook来获取login函数:

jest.mock("../../hooks/useAuth", () => ({
  __esModule: true,
  default: () => ({
    login: jest.fn(() => Promise.resolve()),
  }),
}));

// 导入mock后的自定义Hook
import useAuth from "../../hooks/useAuth";

test("submitting the form calls onSubmit with username and password", async () => {
  // 获取mock的login函数
  const mockedLogin = useAuth().login;
  render(<Login />);
  const username = "name";
  const password = "i need no password";

  await userEvent.type(screen.getByLabelText(/username/i), username);
  await userEvent.type(screen.getByLabelText(/password/i), password);
  await userEvent.click(screen.getByRole("button", { name: /submit/i }));

  expect(mockedLogin).toHaveBeenCalledWith({
    username,
    password,
  });
  expect(mockedLogin).toHaveBeenCalledTimes(1);
});

内容的提问来源于stack exchange,提问作者Arbnor Lumezi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 16:31:33