We've recently updated from Angular 5 to Angular 6, and with it RxJs 6. As part of the migration, the timer usage has changed from:
Observable.timer()
to
timer()
There are a number of places in our tests where we mock timer observables with the following pattern.
let timerObserver: Observer<any>;
beforeEach(() => {
spyOn(Observable, 'timer').and.returnValue(Observable.create(
((observer: Observer<any>) => {
timerObserver = observer;
})
));
});
it(`should not have any notifications by default`, () => {
timerObserver.next('');
...
});
Does anybody know how to migrate this pattern across?
Edit: I've created a simplified illustration of the problem here:
https://stackblitz.com/edit/angular-v6-testing-template-nm7add
// Hello.Component
ngOnInit() {
const timer$ = timer(30);
timer$.subscribe(() => {
this.testMe = 'this has been changed';
});
}
// Hello.component.spec
it('should set testMe after a given timer', fakeAsync(() => {
tick(50);
expect(fixture.componentInstance.testMe).toBe('this has been changed');
}));
In this example, I'm trying to get timer to trigger without waiting for the timer to resolve.
As you are using fakeAsync
, you can rely upon its patching of setInterval
to fake the implementation of the timer
observable.
However, you will need to clobber the asyncScheduler
instance's now
method, as it returns Date.now()
. (Strictly speaking, this isn't necessary for the timer
observable, as you've used it, but it will matter for some other observables - e.g. the observable returned by the delay
operator).
You can get things to work pretty easily if you use beforeEach
and afterEach
to clobber the now
method and to configure a function that keeps track of the fake time:
import { fakeAsync, tick as _tick } from '@angular/core/testing';
import { asyncScheduler, of, timer } from 'rxjs';
import { delay } from 'rxjs/operators';
describe('fakeAsync and RxJS', () => {
let tick: (milliseconds: number) => void;
beforeEach(() => {
let fakeNow = 0;
tick = milliseconds => {
fakeNow += milliseconds;
_tick(milliseconds);
};
asyncScheduler.now = () => fakeNow;
});
it('should support timer with fakeAsync', fakeAsync(() => {
const source = timer(100);
let received: number | undefined;
source.subscribe(value => received = value);
tick(50);
expect(received).not.toBeDefined();
tick(50);
expect(received).toBe(0);
}));
it('should support delay with fakeAsync', fakeAsync(() => {
const source = of(0).pipe(delay(100));
let received: number | undefined;
source.subscribe(value => received = value);
tick(50);
expect(received).not.toBeDefined();
tick(50);
expect(received).toBe(0);
}));
afterEach(() => {
delete asyncScheduler.now;
});
});
Actually, because relying upon fakeAsync
to mock the time-based observable is likely to be useful, I've added a fakeSchedulers
function to my rxjs-marbles
package. See fake-spec.ts
for example usage.
The implementation is basically the same as that in the above snippet - just wrapped up in a function.
Since writing this answer and adding fakeSchedulers
to rxjs-marbles
, I've written an article about Testing with Fake Time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With