测试使用setInterval或setTimeout的Angular2组件 [英] Testing Angular2 components that use setInterval or setTimeout
问题描述
我有一个相当典型的简单ng2组件,它调用服务来获取一些数据(轮播项目)。它还使用setInterval每隔n秒在UI中自动切换轮播幻灯片。它运行得很好,但是当运行Jasmine测试时,我得到错误:不能在异步测试区域内使用setInterval。
I have a fairly typical, simple ng2 component that calls a service to get some data (carousel items). It also uses setInterval to auto-switch carousel slides in the UI every n seconds. It works just fine, but when running Jasmine tests I get the error: "Cannot use setInterval from within an async test zone".
我尝试在此包装setInterval调用.zone.runOutsideAngular(()=> {...}),但错误仍然存在。我原本以为更改测试以在fakeAsync区域中运行会解决问题,但后来我得到一个错误,说不允许在fakeAsync测试区内进行XHR调用(这确实有意义)。
I tried wrapping the setInterval call in this.zone.runOutsideAngular(() => {...}), but the error remained. I would've thought changing the test to run in fakeAsync zone would solve the problem, but then I get an error saying XHR calls are not allowed from within fakeAsync test zone (which does make sense).
如何同时使用服务发出的XHR调用和间隔,同时仍能测试组件?我正在使用ng2 rc4,由angular-cli生成的项目。非常感谢提前。
How can I use both the XHR calls made by the service and the interval, while still being able to test the component? I'm using ng2 rc4, project generated by angular-cli. Many thanks in advance.
我的组件代码:
constructor(private carouselService: CarouselService) {
}
ngOnInit() {
this.carouselService.getItems().subscribe(items => {
this.items = items;
});
this.interval = setInterval(() => {
this.forward();
}, this.intervalMs);
}
并且来自Jasmine规范:
And from the Jasmine spec:
it('should display carousel items', async(() => {
testComponentBuilder
.overrideProviders(CarouselComponent, [provide(CarouselService, { useClass: CarouselServiceMock })])
.createAsync(CarouselComponent).then((fixture: ComponentFixture<CarouselComponent>) => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
// some expectations here;
});
}));
推荐答案
清洁代码是可测试的代码。 setInterval
有时难以测试,因为时机永远不会完美。您应该将 setTimeout
抽象为可以模拟测试的服务。在模拟中,您可以使用控件来处理间隔的每个刻度。例如
Clean code is testable code. setInterval
is sometimes difficult to test because the timing is never perfect. You should abstract the setTimeout
into a service that you can mock out for the test. In the mock you can have controls to handle each tick of the interval. For example
class IntervalService {
interval;
setInterval(time: number, callback: () => void) {
this.interval = setInterval(callback, time);
}
clearInterval() {
clearInterval(this.interval);
}
}
class MockIntervalService {
callback;
clearInterval = jasmine.createSpy('clearInterval');
setInterval(time: number, callback: () => void): any {
this.callback = callback;
return null;
}
tick() {
this.callback();
}
}
使用 MockIntervalService
您现在可以控制每个滴答,这在测试期间更容易推理。还有一个间谍来检查组件被销毁时是否调用 clearInterval
方法。
With the MockIntervalService
you can now control each tick, which is so much more easy to reason about during testing. There's also a spy to check that the clearInterval
method is called when the component is destroyed.
对于你的 CarouselService
,因为它也是异步的,请参阅 这篇文章 获得一个好的解决方案。
For your CarouselService
, since it is also asynchronous, please see this post for a good solution.
以下是使用前面提到的服务的完整示例(使用RC 6)。
Below is a complete example (using RC 6) using the previously mentioned services.
import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TestBed } from '@angular/core/testing';
class IntervalService {
interval;
setInterval(time: number, callback: () => void) {
this.interval = setInterval(callback, time);
}
clearInterval() {
clearInterval(this.interval);
}
}
class MockIntervalService {
callback;
clearInterval = jasmine.createSpy('clearInterval');
setInterval(time: number, callback: () => void): any {
this.callback = callback;
return null;
}
tick() {
this.callback();
}
}
@Component({
template: '<span *ngIf="value">{{ value }}</span>',
})
class TestComponent implements OnInit, OnDestroy {
value;
constructor(private _intervalService: IntervalService) {}
ngOnInit() {
let counter = 0;
this._intervalService.setInterval(1000, () => {
this.value = ++counter;
});
}
ngOnDestroy() {
this._intervalService.clearInterval();
}
}
describe('component: TestComponent', () => {
let mockIntervalService: MockIntervalService;
beforeEach(() => {
mockIntervalService = new MockIntervalService();
TestBed.configureTestingModule({
imports: [ CommonModule ],
declarations: [ TestComponent ],
providers: [
{ provide: IntervalService, useValue: mockIntervalService }
]
});
});
it('should set the value on each tick', () => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
let el = fixture.debugElement.nativeElement;
expect(el.querySelector('span')).toBeNull();
mockIntervalService.tick();
fixture.detectChanges();
expect(el.innerHTML).toContain('1');
mockIntervalService.tick();
fixture.detectChanges();
expect(el.innerHTML).toContain('2');
});
it('should clear the interval when component is destroyed', () => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
fixture.destroy();
expect(mockIntervalService.clearInterval).toHaveBeenCalled();
});
});
这篇关于测试使用setInterval或setTimeout的Angular2组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!