← 中文资源

简体中文版本

测试用 Spy 与 Stub 记录器

无需依赖的轻量测试替身,记录调用参数与结果,支持返回值、Promise 值与重置。

阅读英文原文

功能说明

createSpy 创建一个可调用的测试替身,记录每次调用的参数、this、返回值与抛出的错误,并提供 callCount、calledWith、mockImplementation、mockReturnValue、mockResolvedValue 与 reset。它适合在单元测试中替代网络请求、时钟回调或第三方 SDK,也适合在没有测试框架的脚本里做行为观察。

关键代码

包装函数先创建调用记录并入栈,再通过 apply 执行当前行为,成功时写入 result,失败时记录 error 后重新抛出,保证被测代码仍能看到异常。calls 数组直接暴露,便于断言参数细节。calledWith 用 Object.is 逐项比较,避免 NaN 与正负零的误判。mockResolvedValue 把行为替换为返回 resolved Promise,reset 清空调用记录并恢复初始实现。

使用注意

该替身不包含断言失败信息、快照序列化或自动清理钩子,复杂项目仍应使用 Vitest 或 Jest 的内置 mock。calledWith 只做浅比较,对象参数需要引用相同或自行写匹配函数。调用记录会持有参数与返回值引用,长时间运行的测试应调用 reset 避免内存增长。被 spy 替换后要记得恢复原对象属性,避免测试间污染。

验证方式

调用 spy 并断言 callCount、calls 中的参数与 this,测试 calledWith 对 NaN、正负零和对象引用的行为。替换实现后验证返回值,测试 mockReturnValue 与 mockResolvedValue 的链式调用。让实现抛错,确认错误被记录且继续向上抛出。最后调用 reset,断言记录清空并恢复初始实现。

代码示例

export function createSpy(implementation = () => undefined) {
  if (typeof implementation !== "function") {
    throw new TypeError("implementation must be a function");
  }
  let behavior = implementation;
  const calls = [];
  const spy = function spyWrapper(...args) {
    const call = { args, thisArg: this, result: undefined, error: undefined };
    calls.push(call);
    try {
      call.result = behavior.apply(this, args);
      return call.result;
    } catch (error) {
      call.error = error;
      throw error;
    }
  };
  spy.calls = calls;
  spy.callCount = () => calls.length;
  spy.calledWith = (...expected) => calls.some((call) => {
    return expected.every((value, index) => Object.is(value, call.args[index]));
  });
  spy.mockImplementation = (next) => {
    if (typeof next !== "function") throw new TypeError("next must be a function");
    behavior = next;
    return spy;
  };
  spy.mockReturnValue = (value) => {
    behavior = () => value;
    return spy;
  };
  spy.mockResolvedValue = (value) => {
    behavior = () => Promise.resolve(value);
    return spy;
  };
  spy.reset = () => {
    calls.length = 0;
    behavior = implementation;
    return spy;
  };
  return spy;
}