Angular 2 fakeAsync 在使用tick()的函数中等待超时?
Posted
技术标签:
【中文标题】Angular 2 fakeAsync 在使用tick()的函数中等待超时?【英文标题】:Angular 2 fakeAsync waiting for timeout in a function using tick()? 【发布时间】:2017-08-21 00:22:06 【问题描述】:我正在尝试从 Angular 2 中的模拟后端获取结果以进行单元测试。目前,我们使用带有超时的fakeAsync
来模拟时间的流逝。
当前工作单元测试
it('timeout (fakeAsync/tick)', fakeAsync(() =>
counter.getTimeout();
tick(3000); //manually specify the waiting time
));
但是,这意味着我们仅限于手动定义的超时。不是在异步任务完成时。我想做的是让tick()
等到任务完成后再继续测试。
这似乎没有按预期工作。
阅读fakeAsync
和tick
的答案here 解释说:
tick() 模拟时间的异步流逝。
我设置了一个plnkr example 来模拟这种情况。
在这里,我们调用了getTimeout()
方法,该方法调用了一个有超时的内部异步任务。在测试中,我们尝试包装它并在调用getTimeout()
方法后调用tick()
。
counter.ts
getTimeout()
setTimeout(() =>
console.log('timeout')
,3000)
counter.specs.ts
it('timeout (fakeAsync/tick)', fakeAsync(() =>
counter.getTimeout();
tick();
));
但是,单元测试失败并出现错误“错误:1 个计时器仍在队列中。”
issue here in the angular repo 跟这个有关系吗?
是否可以这样使用tick()
来等待超时功能?还是我可以使用另一种方法?
【问题讨论】:
我试图用一个简单的async/await
助手来解决这个问题。不幸的是,虽然测试通过并正常运行,但 zone.js 却很糟糕,窒息而死。一个人应该能够编写一个简单的服务测试,而无需所有这些框架特定的废话。
任何人都可以看看这个***.com/questions/63580184/…
【参考方案1】:
我通常在单元测试中使用 flushMicrotasks 方法来与我的服务一起使用。我读过 tick() 与 flushMicrotasks 非常相似,但也调用了 jasmine tick() 方法。
【讨论】:
【参考方案2】:试试这个:
// I had to do this:
it('timeout (fakeAsync/tick)', (done) =>
fixture.whenStable().then(() =>
counter.getTimeout();
tick();
done();
);
);
Source
【讨论】:
【参考方案3】:异步
test.service.ts
export class TestService
getTimeout()
setTimeout(() => console.log("test") , 3000);
test.service.spec.ts
import TestBed, async from '@angular/core/testing';
describe("TestService", () =>
let service: TestService;
beforeEach(() =>
TestBed.configureTestingModule(
providers: [TestService],
);
service = TestBed.get(TestService);
);
it("timeout test", async(() =>
service.getTimeout();
);
);
假异步
test.service.ts
export class TestService
readonly WAIT_TIME = 3000;
getTimeout()
setTimeout(() => console.log("test") , this.WAIT_TIME);
test.service.spec.ts
import TestBed, fakeAsync from '@angular/core/testing';
describe("TestService", () =>
let service: TestService;
beforeEach(() =>
TestBed.configureTestingModule(
providers: [TestService],
);
service = TestBed.get(TestService);
);
it("timeout test", fakeAsync(() =>
service.getTimeout();
tick(service.WAIT_TIME + 10);
);
);
【讨论】:
【参考方案4】:在每个测试结束时添加:
fixture.destroy();
flush();
【讨论】:
它将等待任务从 3 个变为 1 个。但仍有一个在队列中。【参考方案5】:fakeAsync
的目的是在您的规范内控制时间。 tick
不会等待任何时间,因为它是用于模拟时间流逝的同步函数。如果您想等到异步功能完成,您将需要使用async
和whenStable
,但是,在您的示例中,规范需要 3 秒才能通过,所以我不建议这样做。
counter.spec.ts 失败的原因是您只模拟了 0 秒的经过(通常用于表示事件循环的下一个刻度)。因此,当规范完成时,仍然有模拟的计时器处于活动状态,这使整个规范失败。通过通知您超时已被模拟并且未处理,它实际上可以正常工作。
基本上,我认为您正在尝试以不打算使用它们的方式使用fakeAsync
和tick
。如果您需要按照您建议的方式测试超时,最简单的方法是自己模拟 setTimeout
函数,这样无论使用多少时间,您都可以调用该方法。
已编辑 我遇到了一个相关的问题,我想清除计时器,因为它不是被测试的部分,所以我不在乎花了多长时间。我试过了:
tick(Infinity);
这很有效,但是超级hacky。我最终选择了
discardPeriodicTasks();
我所有的计时器都被清除了。
【讨论】:
尝试了一切,但discardPeriodicTasks()
工作完美
只是为了节省时间,discardPeriodicTasks
在 @angular/core/testing
【参考方案6】:
尝试在测试结束时添加以下一个或多个函数调用:
flush();
flushMicrotasks();
discardPeriodicTasks();
flush
(带有可选的 maxTurns 参数)也刷新宏任务。 (Angular 测试教程中没有提到这个功能。)
flushMicrotasks
刷新微任务队列。
discardPeriodicTasks
取消“仍在队列中的周期性计时器”。
队列中的计时器并不一定意味着您的代码有问题。例如,观察当前时间的组件可能会引入这样的计时器。如果您使用来自外国库的此类组件,您可能还会考虑存根它们而不是“追逐计时器”。
为了进一步了解,您可以查看zone-testing.js
中fakeAsync
函数的javascript 代码。
【讨论】:
我不得不同时使用flush() 和disacrdPeriodicTasks() 来解决这个问题。希望我知道到底发生了什么,但是嘿嗬 :-)。 刚刚发现我的测试中多余的 fakeAsync 导致它失败,并出现“计时器仍在队列中”。删除 fakeAsync 修复了测试。 有时使用区域代码进行深度调试会话可以解释这种奇怪的行为并揭示待处理的任务,但前提是您有足够的时间...【参考方案7】:对我来说,以上都没有帮助,但在我的测试代码中两次调用 tick(<async_time>)
。
到目前为止我的解释:对于每个异步调用,您都需要一个/自己的 tick()-调用。
之后我有一个.pipe(debounceTime(500))
和一个timer(500).subscribe(..)
,这对我有帮助:
tick(500);
tick(500);
【讨论】:
以上是关于Angular 2 fakeAsync 在使用tick()的函数中等待超时?的主要内容,如果未能解决你的问题,请参考以下文章
angular2 测试中 fakeAsync 的 tick() 和 done() 有啥区别?
为使用 Observables 的 Angular 2 组件编写 Jasmine 测试
Angular2 NgModel 在 Jasmine 测试中没有获得价值