DEBUG.WIKI
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

Mocha 已跑完仍不退出,检查未等待的 tickAsync

Mocha 完成后 Node 仍不退出,可能是 nextAsync 模式下未等待 tickAsync 就卸载时钟。对照 fake-timers 15.3.1、15.3.2 与 15.4.0,修正等待顺序及卸载保护。

Mocha 已经打印所有用例完成,Node 进程却不退出。若测试使用 fake-timers 的 nextAsync 模式,同时调用 clock.tickAsync() 却没有等待它,检查时钟是否在推进 Promise 完成前就被卸载。

15.3.1、15.3.2 的相关实现会在异步推进期间暂时切到 manual,待 Promise 结束后恢复 nextAsync。如果定时器回调先调用了 Mocha 的 done,afterEach 随即 uninstall,而推进的 finally 后执行,旧时钟可能重新启动自动推进循环。

用例应等待自己启动的推进操作

不要用 done 宣告完成,同时把 tickAsync 的 Promise 留在后台。已有测试可改为异步用例,等待推进与原断言完成,再由 afterEach 卸载。例如:

const assert = require('node:assert/strict');
const FakeTimers = require('@sinonjs/fake-timers');

let clock;
beforeEach(() => {
  clock = FakeTimers.install();
  clock.setTickMode({ mode: 'nextAsync' });
});
afterEach(() => clock.uninstall());

it('等待定时工作完成', async () => {
  let completed = false;
  setTimeout(() => { completed = true; }, 500);
  await clock.tickAsync(500);
  assert.equal(completed, true);
});

这是 Mocha 测试文件的片段,保留项目原来的加载方式。不要在同一个用例里既返回 Promise 又使用 done,也不要为了让测试结束去掉本来必须等待的业务 Promise。

如果已经选用自动 nextAsync,可以检查手动 tickAsync 是否确实有必要;需要精确推进时刻的测试,也可按其目的统一使用 manual。两种策略应明确组织,不能在多个辅助函数里各自切换时钟模式而不等待。

正式 15.4.0 增加了卸载后的保护

15.4.0 的 pauseAutoTickUntilFinished 在 finally 中先检查 uninstalled,未卸载才恢复 nextAsync。沿兼容范围采用包含这一保护的正式版本,可避免该位置在卸载后重启时钟;但库的防御不代替用例等待自己创建的异步操作。

仅把 tickAsync 改为同步 tick 也可能改变结果,因为两者对原生 Promise 回调获得执行机会的安排不同。若业务依赖 Promise 与 timer 交替,不应为了退出进程盲目替换。

回查失败用例的完成信号、afterEach 的卸载时刻与所有推进 Promise。验收不仅要看断言通过,还要确认套件结束后进程能正常退出,下一条用例没有继承旧时钟;不要把 Mocha 的强制退出选项当成这个生命周期问题的修复。