简体   繁体   中英

Test rxjs interval in Angular 6 component ngOnInit function

I have a component with the following ngOnInit function which polls a service method for status updates:

ngOnInit() {
  interval(2000).pipe(
    switchMap(() => this.dataService.getStatus())
  ).subscribe((result) => {
    this.uploadStatus = result;
  );
}

I am trying to test that the updates are actually happening using the following code:

beforeEach(() => {
  fixture = TestBed.createComponent(UploadComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();
});


it('should start checking for status updates', fakeAsync(() => {
  const dataService = TestBed.get(DataService);
  // Mock the getStatus function
  spyOn(dataService, 'getStatus').and.returnValue(Observable.create().pipe(map(() => 'woo')));
  // Should not be initialised yet
  expect(component.uploadStatus).toBeUndefined();
  tick(2000);
  expect(component.uploadStatus).toBe('woo');
}));

However component.uploadStatus is always null. How should I go about testing this type of scenario? Ideally I would like to check for multiple updates over time.

Thanks

What I eventually settled on was the following. I also had to store the subscription so that I can cancel it at the end of the test to prevent "periodic timers still in queue" errors:

ngOnInit() {
  // Store the subscription so we can unsubscribe when testing
  this.pollingSubscription = interval(2000).pipe(
    switchMap(() => this.dataService.getStatus())
  ).subscribe((result) => {
    this.uploadStatus = result;
  });
}

Then test as follows:

it(`init`, fakeAsync(inject([DataService],
  (dataService: DataService) => {
    const testReturns = [
      of('woo'),
      of('yay')
    ];
    let currentReturn = -1;
    spyOn(dataService, 'getStatus').and.callFake(() => {
      currentReturn++;
      return testReturns[currentReturn];
    });

    expect(component.uploadStatus).toBeUndefined();
    fixture.detectChanges();
    // expect(component.uploadStatus).toBe('Login');
    // spyOn(dataService, 'getStatus').and.returnValue(of('woo'));
    component.ngOnInit();
    tick(2000);
    expect(component.uploadStatus).toBe('woo');
    tick(2000);
    expect(component.uploadStatus).toBe('yay');

    // Unsubscribe to prevent the "periodic timers still in queue" errors
    component.pollingSubscription.unsubscribe();
})));

The problem is in the beforeEach of the unit test template the Angular CLI set up for you. You subscribe to the Observable created by interval during the first change detection cycle, ie, in ngOnInit .

The subscription must take place inside the fakeAsync zone in order for tick to manage the Observable's time. Move the call to fixture.detectChanges inside the fakeAsync zone and you will see tick now manages the time.

beforeEach((): void => {
  fixture = TestBed.createComponent(UploadComponent);
  component = fixture.componentInstance;
  // Get rid of call to detectChanges
});


it('should start checking for status updates', fakeAsync((): void => {
  // Arrange
  const dataService = TestBed.get(DataService);
  spyOn(dataService, 'getStatus').and.returnValue(of('woo'));
  
  // Assert
  expect(component.uploadStatus).toBeUndefined();

  // Act
  fixture.detectChanges();
  tick(2000);

  // Assert
  expect(component.uploadStatus).toBe('woo');

  // Clean up RxJS.interval function
  discardPeriodicTasks();
}));

you have to trigger change detection after tick, this should work

tick(2000);
fixture.detectChanges();
expect(component.uploadStatus).toBe('woo');

You may use setTimeout() to wait for 2 seconds (slightly more because you are again doing asynchronous task inside), and then check the property value.

Make sure to use done() method to notify the test runner that test case is completed.

it('should start checking for status updates', (done) => {
  const dataService = TestBed.get(DataService);
  // Mock the getStatus function
  spyOn(dataService, 'getStatus').and.returnValue(Observable.create().pipe(map(() => 'woo')));
  // Should not be initialised yet
  expect(component.uploadStatus).toBeUndefined();

  setTimeout(()=> {
        expect(component.uploadStatus).toBe('woo');
        done();
  },2500);

});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM