Javascript 测试使用setInterval或setTimeout的Angular2组件

Javascript 测试使用setInterval或setTimeout的Angular2组件,javascript,unit-testing,typescript,angular,jasmine,Javascript,Unit Testing,Typescript,Angular,Jasmine,我有一个相当典型、简单的ng2组件,它调用一个服务来获取一些数据(转盘项目)。它还使用setInterval每n秒自动切换UI中的旋转幻灯片。它工作得很好,但是当运行Jasmine测试时,我得到一个错误:“不能在异步测试区域内使用setInterval” 我尝试在这个.zone.runOutsideAngular(()=>{…})中包装setInterval调用,但错误仍然存在。我本以为将测试更改为在fakeAsync区域中运行可以解决问题,但随后我收到一个错误,即不允许从fakeAsync测试

我有一个相当典型、简单的ng2组件,它调用一个服务来获取一些数据(转盘项目)。它还使用setInterval每n秒自动切换UI中的旋转幻灯片。它工作得很好,但是当运行Jasmine测试时,我得到一个错误:“不能在异步测试区域内使用setInterval”

我尝试在这个.zone.runOutsideAngular(()=>{…})中包装setInterval调用,但错误仍然存在。我本以为将测试更改为在fakeAsync区域中运行可以解决问题,但随后我收到一个错误,即不允许从fakeAsync测试区域中进行XHR调用(这是有意义的)

在仍然能够测试组件的情况下,如何使用服务发出的XHR调用和间隔?我使用的是ng2 rc4,angular cli生成的项目。非常感谢

组件中的我的代码:

constructor(private carouselService: CarouselService) {
}

ngOnInit() {
    this.carouselService.getItems().subscribe(items => { 
        this.items = items; 
    });
    this.interval = setInterval(() => { 
        this.forward();
    }, this.intervalMs);
}
从茉莉花的规格:

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;
    });
}));
it('应显示转盘项目',异步(()=>{
testComponentBuilder
.overrideProviders(CarouselComponent,[提供(CarouselService,{useClass:CarouselService Mock})])
.createAsync(CarouselComponent)。然后((fixture:ComponentFixture)=>{
fixture.detectChanges();
让compiled=fixture.debugElement.nativeElement;
//这里有一些期望;
});
}));

使用可观察的?
要测试它们,应该使用.toPromise()方法

干净的代码是可测试的代码<代码>设置间隔有时很难测试,因为计时从不完美。您应该将
setTimeout
抽象为一个服务,您可以为测试模拟该服务。在模拟中,您可以使用控件来处理间隔的每个刻度。比如说

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
方法

对于您的
CarouselService
,由于它也是异步的,请参阅以获得良好的解决方案

下面是使用上述服务的完整示例(使用RC 6)

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();
  });
});
从'@angular/core'导入{Component,OnInit,OnDestroy};
从“@angular/common”导入{CommonModule};
从“@angular/core/testing”导入{TestBed};
类间服务{
间隔
setInterval(时间:number,回调:()=>void){
this.interval=setInterval(回调,时间);
}
clearInterval(){
clearInterval(这个.interval);
}
}
类间服务{
回调;
clearInterval=jasmine.createSpy('clearInterval');
setInterval(时间:number,回调:()=>void):任意{
this.callback=回调;
返回null;
}
勾选(){
这是callback();
}
}
@组成部分({
模板:“{value}}”,
})
类TestComponent实现OnInit、OnDestroy{
价值
构造函数(私有_intervalService:intervalService){}
恩戈尼尼特(){
设计数器=0;
此.u intervalService.setInterval(1000,()=>{
this.value=++计数器;
});
}
恩贡德斯特罗(){
这是.u intervalService.clearInterval();
}
}
描述('component:TestComponent',()=>{
让mockIntervalService:mockIntervalService;
在每个之前(()=>{
mockIntervalService=新的mockIntervalService();
TestBed.configureTestingModule({
导入:[CommonModule],
声明:[TestComponent],
供应商:[
{提供:IntervalService,使用值:mockIntervalService}
]
});
});
它('应该在每个刻度上设置值',()=>{
让fixture=TestBed.createComponent(TestComponent);
fixture.detectChanges();
设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');
});
它('应该清除组件销毁时的间隔',()=>{
让fixture=TestBed.createComponent(TestComponent);
fixture.detectChanges();
fixture.destroy();
expect(mockIntervalService.clearInterval).toHaveBeenCalled();
});
});

我也遇到了同样的问题:特别是,当第三方服务从测试调用
setInterval()
时,出现此错误:

错误:无法在异步区域测试中使用setInterval

您可以模拟调用,但这并不总是可取的,因为您可能实际上想要测试与另一个模块的交互

在我的例子中,我用Jasmine的(>=2.0)而不是Angulars的
async()
,解决了这个问题:


太棒了,这正是我需要知道的。我需要改变的一件事是在MockIntervalService中:“callback;”改为“callback(){}”。否则将导致“TypeError:this.callback不是函数”。非常感谢!这是一个好主意,效果很好。让服务匹配setTimeout和setInterval参数,而不是将它们交换,这样做更有意义。此外,该代码不能有两个间隔,因为如果以后需要清除它们,可能会丢失它们的ID。为此,我想让IntervalService只是真实方法的一个副本。我想确认,对于那些正在编写新代码的人来说,Observable.timer()/Observable.interval()可能是首选选项。在任何情况下,都会出现需要处理setTimeout/setInterval的情况,例如导入的库。我看到了一个非常类似的问题--
fixture.whenStable()
永远无法解决--无论我使用的是
Observable.interval
还是
setInterval
。夹具应该在间隔之间保持稳定,对吗?但它从来都不被认为是稳定的。这只是一个解决方案,如果您不使用依赖
组件的第三方测试助手(如)
it('Test MyAsyncService', (done) => {
  var myService = new MyAsyncService()
  myService.find().timeout(1000).toPromise() // find() returns Observable.
    .then((m: any) => { console.warn(m); done(); })
    .catch((e: any) => { console.warn('An error occured: ' + e); done(); })
  console.warn("End of test.")
});