i have typical, simple ng2 component calls service data (carousel items). uses setinterval auto-switch carousel slides in ui every n seconds. works fine, when running jasmine tests error: "cannot use setinterval within async test zone".
i tried wrapping setinterval call in this.zone.runoutsideangular(() => {...}), error remained. would've thought changing test run in fakeasync zone solve problem, error saying xhr calls not allowed within fakeasync test zone (which make sense).
how can use both xhr calls made service , interval, while still being able test component? i'm using ng2 rc4, project generated angular-cli. many in advance.
my code component:
constructor(private carouselservice: carouselservice) { } ngoninit() { this.carouselservice.getitems().subscribe(items => { this.items = items; }); this.interval = setinterval(() => { this.forward(); }, this.intervalms); }
and 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; // expectations here; }); }));
clean code testable code. setinterval
difficult test because timing never perfect. should abstract settimeout
service can mock out test. in mock can have controls handle each tick of interval. 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): { this.callback = callback; return null; } tick() { this.callback(); } }
with mockintervalservice
can control each tick, more easy reason during testing. there's spy check clearinterval
method called when component destroyed.
for carouselservice
, since asynchronous, please see this post solution.
below complete example (using rc 6) using mentioned services.
import { component, oninit, ondestroy } '@angular/core'; import { commonmodule } '@angular/common'; import { testbed } '@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): { 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 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 interval when component destroyed', () => { let fixture = testbed.createcomponent(testcomponent); fixture.detectchanges(); fixture.destroy(); expect(mockintervalservice.clearinterval).tohavebeencalled(); }); });
Comments
Post a Comment