Deno mock.reset 后方法仍被替换,显式恢复 mock 并核对清理范围
Deno 的 mock.reset 曾遗漏模拟状态清理,2.9.7 的方法替身仍可能保留原替换。可显式 restoreAll 恢复当前 tracker 的方法,定时器状态须分别核对。
第二个测试仍然调用到前一个测试的替身实现,即使中间已经执行 mock.reset(),可以检查 Deno 的 node:test 方法恢复路径。调用记录变成零,不等于对象上的方法已经恢复。
import assert from 'node:assert/strict';
import { mock } from 'node:test';
const service = { read: () => 'original' };
mock.method(service, 'read', () => 'mocked');
assert.equal(service.read(), 'mocked');
mock.reset();
assert.equal(service.read(), 'original');
这段方法替换在 Deno 2.9.7 有明确失败案例:最后得到的仍然是 mocked。此时不必先怀疑两个测试共享了模块缓存;对象方法的属性描述符尚未被恢复,下一次调用自然继续进入替身。
这一清理入口也曾遗漏模拟定时器的重置。定时器与方法替换的内部对象不同,要分别检查:旧环境中再次启用定时器可能报已启用,而 2.9.7 的方法案例表现为旧实现残留。不能因为其中一项修好了,就认为 reset() 已覆盖所有模拟状态。
2.9.7 的 reset() 遍历已跟踪对象,调用各自的 resetCalls()。方法模拟对象的这一操作只清空调用记录,没有执行用于恢复原属性的回调;restore() 才会恢复描述符并从跟踪集合移除该模拟。因此,可以在清理时显式恢复方法:
mock.method(service, 'read', () => 'mocked');
try {
assert.equal(service.read(), 'mocked');
} finally {
mock.restoreAll();
}
assert.equal(service.read(), 'original');
将恢复放在 finally,可以让断言失败时也进入清理。restoreAll() 针对当前 tracker 跟踪的替身,不是任意全局状态的回滚器。使用单独 tracker 或测试上下文时,应通过创建该模拟的对应对象清理,不要把两个 tracker 混用。
如果测试还启用了模拟定时器,也核对该版本支持的 mock.timers.reset(),不要把恢复方法实现当成恢复时钟。调用历史、替身实现和定时器状态应按测试需要分别验收。
目前不指定一个已正式发行的版本来解决这里的方法恢复缺陷。临时方案是使用现有、可核的恢复入口,并在原版本执行最小例子。验收要检查原方法真的重新生效,再运行原来的两项测试;只检查 mock 的调用次数清零,仍可能让错误实现进入后一项测试。


