I am unsuccessful in writing a unit test for an RxJS timer that is displayed in the template with an AsyncPipe.
Component:
import { Component } from '@angular/core';
import { AsyncPipe, NgIf } from '@angular/common';
import { map, timer } from 'rxjs';
@Component({
selector: 'app-root',
template: `
Timer should fire after {{ delay }} seconds:
<ng-container *ngIf="timer$ | async as timer; else waiting">Timer fired {{ timer }} times</ng-container>
<ng-template #waiting>Waiting for Timer…</ng-template>
`,
imports: [AsyncPipe, NgIf],
})
export class AppComponent {
delay = 3;
timer$ = timer(this.delay * 1000, 1000).pipe(map((n) => n + 1));
}
Test:
import { AppComponent } from './main';
import {
ComponentFixture,
TestBed,
} from '@angular/core/testing';
import { firstValueFrom } from 'rxjs';
describe('AppComponent', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should initially render waiting', () => {
expect(fixture.nativeElement.textContent).toContain('Waiting'); //passes
});
it('should emit timer', async () => {
expect(await firstValueFrom(component.timer$)).toBe(1); //passes
});
it('should show "Timer fired"', () => {
jest.useFakeTimers();
jest.advanceTimersByTime(5000);
expect(fixture.nativeElement.textContent).toContain('Timer fired'); //fails
});
});
Stackblitz: https://stackblitz.com/edit/angular-rxjs-timer-asyncpipe-unit-test?file=src%2Fmain.ts,src%2Ftest.spec.ts
The most similar question here on StackOverflow is this one: Angular testing async pipe does not trigger the observable Unfortunately, the solution described in the most upvoted answer does not solve the problem.
We can use fakeAsync
and tick
of @angular/core/testing
to simulate the time lapse.
Then we can use fixture.whenStable()
to check the view.
it('should show "Timer fired"', fakeAsync((done: jest.DoneCallback) => {
expect.assertions(1);
tick(3000);
fixture.whenStable().then(() => {
expect(fixture.nativeElement.textContent).toContain('Timer fired');
done();
});
}));
The same test rewritten with async/await seems to be the exact same but does not work:
it('should show "Timer fired"', fakeAsync(async () => {
tick(3000);
await fixture.whenStable();
expect(fixture.nativeElement.textContent).toContain('Timer fired');
}));