功能说明
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;
}