2016-08-01 19 views
12

Tôi có thành phần ng2 khá đơn giản, điển hình gọi một dịch vụ để lấy một số dữ liệu (các mục băng chuyền). Nó cũng sử dụng setInterval để tự động chuyển đổi các trang trình bày băng chuyền trong UI mỗi n giây. Nó hoạt động tốt, nhưng khi chạy thử nghiệm Jasmine tôi nhận được lỗi: "Không thể sử dụng setInterval từ bên trong một vùng thử nghiệm async".Thử nghiệm thành phần Angular2 sử dụng setInterval hoặc setTimeout

Tôi đã thử gói cuộc gọi setInterval trong this.zone.runOutsideAngular (() => {...}), nhưng vẫn còn lỗi. Tôi đã có thể nghĩ rằng việc thay đổi thử nghiệm để chạy trong vùng fakeAsync sẽ giải quyết vấn đề, nhưng sau đó tôi nhận được một lỗi nói rằng XHR cuộc gọi không được phép từ bên trong vùng thử nghiệm fakeAsync (mà không có ý nghĩa).

Làm cách nào để sử dụng cả cuộc gọi XHR do dịch vụ và khoảng thời gian thực hiện, trong khi vẫn có thể kiểm tra thành phần? Tôi đang sử dụng ng2 rc4, dự án được tạo ra bởi angular-cli. Rất cám ơn trước.

Mã của tôi từ các thành phần:

constructor(private carouselService: CarouselService) { 
} 

ngOnInit() { 
    this.carouselService.getItems().subscribe(items => { 
     this.items = items; 
    }); 
    this.interval = setInterval(() => { 
     this.forward(); 
    }, this.intervalMs); 
} 

Và từ spec Jasmine:

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; 
    }); 
})); 

Trả lời

9

Mã sạch là mã có thể kiểm tra. setInterval đôi khi khó kiểm tra vì thời gian không bao giờ là hoàn hảo. Bạn nên tóm tắt các setTimeout thành một dịch vụ mà bạn có thể thử cho thử nghiệm. Trong giả lập, bạn có thể có các điều khiển để xử lý từng dấu tích của khoảng thời gian. Ví dụ:

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(); 
    } 
} 

Với MockIntervalService giờ đây bạn có thể kiểm soát từng dấu tích, điều này dễ hiểu hơn nhiều trong quá trình thử nghiệm. Ngoài ra còn có một gián điệp để kiểm tra rằng phương pháp clearInterval được gọi khi thành phần bị hủy.

Đối với số CarouselService của bạn, vì nó cũng không đồng bộ, vui lòng xem this post để có giải pháp tốt.

Dưới đây là ví dụ hoàn chỉnh (sử dụng RC 6) sử dụng các dịch vụ được đề cập trước đó.

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(); 
    }); 
}); 
+0

Rực rỡ, đây chính là điều tôi cần biết. Một điều mà tôi cần thay đổi là trong MockIntervalService: "callback;" gọi lại() { }". Nếu không, nó sẽ dẫn đến "TypeError: this.callback không phải là một hàm". Cảm ơn nhiều! –

+1

Đây là một ý tưởng tuyệt vời và hoạt động tốt. Nó sẽ có ý nghĩa hơn để làm cho một dịch vụ phù hợp với các đối số setTimeout và setInterval thay vì thay đổi chúng. – jsgoupil

+0

Ngoài ra, bạn không thể có hai khoảng thời gian với mã đó vì bạn có thể mất dấu ID nếu bạn cần xóa chúng sau này. Cho rằng tôi sẽ cho IntervalService chỉ là một bản sao của các phương pháp thực sự. – Dunos

1

gì về việc sử dụng quan sát được? https://github.com/angular/angular/issues/6539 Để kiểm tra, bạn nên sử dụng phương thức .toPromise()

+0

Tôi muốn xác nhận thực sự đối với những người đang viết mã mới, Observable.timer ()/Observable.interval() có thể là tùy chọn ưa thích. Trong mọi trường hợp, sẽ vẫn có các tình huống mà setTimeout/setInterval cần phải được xử lý, ví dụ: thư viện đã nhập. –

3

tôi đã cùng một vấn đề: đặc biệt, nhận errror này khi một dịch vụ của bên thứ ba được gọi setInterval() từ một thử nghiệm:

Error: Cannot use setInterval from within an async zone test.

Bạn có thể thử ra các cuộc gọi, nhưng đó không phải lúc nào cũng mong muốn , vì bạn thực sự có thể muốn kiểm tra tương tác với một mô-đun khác.

Tôi giải quyết nó trong trường hợp của tôi bằng cách chỉ sử dụng của Jasmine (> = 2.0) async support thay vì của Angulars async():

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.") 
}); 
Các vấn đề liên quan