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

如何在Jest中测试作为参数传入的函数是否被调用

问题描述

我有一个接收函数作为参数的函数,想要验证这个参数函数是否被正确调用。

待测试函数

const loadNamespaces = (setNamespaces) => {
  namespaceAPI.getNamespaces().then(namespaces => {
    setNamespaces(namespaces);
  });
}

我的核心需求是断言mockSetNamespaces已被调用。我能通过jest.spyOn模拟并断言namespaceAPI.getNamespaces被调用,但没法用同样方式断言mockSetNamespaces被调用:

测试代码(首次尝试)

test("loadNamespaces", () => {
  const mockSetNamespaces = jest.fn();

  const mockNamespaces = [
    { endpoint: "mock namespace 1", rooms: [] },
  ];

  jest.spyOn(namespaceAPI, "getNamespaces").mockImplementation(() => {
    return new Promise((resolve) => {
      resolve(mockNamespaces);
    });
  });

  SocketIOActions.loadNamespaces(mockSetNamespaces);

  expect(namespaceAPI.getNamespaces).toHaveBeenCalled();
  expect(mockSetNamespaces).toHaveBeenCalled();
});

Jest返回的错误信息

● loadNamespaces

    expect(jest.fn()).toHaveBeenCalled()

    Expected number of calls: >= 1
    Received number of calls:    0

我还试过把setNamespaces放进对象里,想用jest.spyOn方法,但还是没法断言该方法被调用:

测试代码(第二次尝试)

test("loadNamespaces", () => {
  const mockObject = {
    mockSetNamespaces: jest.fn(),
  };

  const mockNamespaces = [
    { endpoint: "mock namespace 1", rooms: [] },
  ];

  jest.spyOn(namespaceAPI, "getNamespaces").mockImplementation(() => {
    return new Promise((resolve) => {
      resolve(mockNamespaces);
    });
  });

  jest.spyOn(mockObject, "mockSetNamespaces").mockImplementation(() => {
    console.log("Hello from spy function");
  });

  SocketIOActions.loadNamespaces(mockObject.mockSetNamespaces);

  expect(namespaceAPI.getNamespaces).toHaveBeenCalled();
  expect(mockObject.mockSetNamespaces).toHaveBeenCalled();
});

有日志显示mock函数实际已经被调用:

console.log
      Hello from spy function

这是Jest的预期行为吗?有没有更简洁的实现方式?


解决方案

这是Jest的正常行为,问题出在测试代码没有等待异步操作完成就执行了断言。loadNamespaces里的namespaceAPI.getNamespaces()返回Promise,then回调里的setNamespaces调用是异步执行的,而你的测试代码是同步执行断言,此时Promise还未resolve,setNamespaces自然还没被调用。

下面是三种可行的解决方法,按需选择:

方法1:使用async/await

将测试函数改为异步函数,等待异步操作完成后再执行断言。可以直接等待mock的getNamespaces返回的Promise,或用mockResolvedValue简化Promise创建:

test("loadNamespaces", async () => {
  const mockSetNamespaces = jest.fn();
  const mockNamespaces = [{ endpoint: "mock namespace 1", rooms: [] }];

  // 用mockResolvedValue简化Promise返回
  const mockGetNamespaces = jest.spyOn(namespaceAPI, "getNamespaces")
    .mockResolvedValue(mockNamespaces);

  SocketIOActions.loadNamespaces(mockSetNamespaces);
  await mockGetNamespaces(); // 等待Promise完成

  expect(namespaceAPI.getNamespaces).toHaveBeenCalled();
  // 还能断言传入的参数是否正确
  expect(mockSetNamespaces).toHaveBeenCalledWith(mockNamespaces);
});

方法2:使用Jest的done回调

通过测试参数done标记测试完成时机,在setNamespaces的mock回调里调用done:

test("loadNamespaces", (done) => {
  const mockSetNamespaces = jest.fn((namespaces) => {
    expect(namespaces).toEqual([{ endpoint: "mock namespace 1", rooms: [] }]);
    done(); // 调用done表示异步操作完成,测试结束
  });

  jest.spyOn(namespaceAPI, "getNamespaces")
    .mockResolvedValue([{ endpoint: "mock namespace 1", rooms: [] }]);

  SocketIOActions.loadNamespaces(mockSetNamespaces);
  expect(namespaceAPI.getNamespaces).toHaveBeenCalled();
});

方法3:手动触发所有Promise

用jest.runAllPromises()强制执行所有待处理的异步Promise,再执行断言:

test("loadNamespaces", () => {
  const mockSetNamespaces = jest.fn();
  const mockNamespaces = [{ endpoint: "mock namespace 1", rooms: [] }];

  jest.spyOn(namespaceAPI, "getNamespaces").mockResolvedValue(mockNamespaces);

  SocketIOActions.loadNamespaces(mockSetNamespaces);
  jest.runAllPromises(); // 触发所有待处理的Promise

  expect(namespaceAPI.getNamespaces).toHaveBeenCalled();
  expect(mockSetNamespaces).toHaveBeenCalled();
});

简化优化点

用mockResolvedValue代替手动创建Promise,能大幅简化mock代码,避免冗余的Promise构造逻辑。

内容的提问来源于stack exchange,提问作者Gustavo Motta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 13:55:36