Jasmine测试报队列仍有计时器错误 RxJS长按监听函数用例排查
报错原因
- Jasmine 配套的
fakeAsync工具会在测试运行时劫持全局的setTimeout方法,所有创建的定时器都会被记录到内部任务队列中。测试执行结束时如果队列中存在未执行的定时器,就会抛出Error: 1 timer(s) still in the queue.错误。 - 第二个用例中,触发
pointerdown后创建了500ms的定时器,仅tick(300)后就触发pointerup执行clearTimeout:原生的clearTimeout只会阻止定时器回调执行,不会将该定时器记录从fakeAsync的内部队列中移除,测试结束时队列中残留了剩余200ms的定时器记录,因此触发报错。 - 额外隐患:测试结束后没有主动取消Observable订阅,导致
finalize中的事件监听器清理逻辑不会执行,存在测试资源泄漏问题。
修复方案
只需要在第二个用例结束前显式清空fakeAsync的剩余定时器队列,同时主动取消订阅清理资源即可,修改后的测试用例代码如下:
it('2. should not emit if pressing on target for less than 500ms', fakeAsync(() => { const target = document.createElement('button'); const longPressListenerSpy = jasmine.createSpy('longPressListenerSpy'); // 存储订阅实例用于后续清理 const subscription = watchForLongPress(target).subscribe({ next: longPressListenerSpy }); target.dispatchEvent(new Event('pointerdown')); tick(300); target.dispatchEvent(new Event('pointerup')); // 丢弃fakeAsync队列中所有剩余的定时器任务 discardPeriodicTasks(); // 主动取消订阅,触发finalize逻辑移除事件监听器 subscription.unsubscribe(); expect(longPressListenerSpy).toHaveBeenCalledTimes(0); }));
如果不想调用discardPeriodicTasks(),也可以选择在触发pointerup后再tick(200)走完剩余的定时器时长,清空队列的效果完全一致。
可选优化(非必须)
如果要从根源避免这类定时器测试问题,可以将原函数的原生定时器替换为RxJS内置的timer操作符,配合RxJS测试调度器执行用例,完全对齐响应式编程范式,无需依赖fakeAsync的定时器劫持逻辑。
内容的提问来源于stack exchange,提问作者Trash Can
相关产品推荐
相关产品推荐

