I have a pipe that helps return the state of an observable.
import {Pipe, PipeTransform} from '@angular/core';
import {Observable, of} from 'rxjs';
import {catchError, map, startWith} from 'rxjs/operators';
/** Specifies the status of an Observable. */
export interface ObservableStatus<T> {
loading?: boolean;
value?: T;
error?: boolean;
}
/** Returns the status {@code ObservableStatus} of a given Observable. */
@Pipe({name: 'getObserverStatus'})
export class ObservableStatusPipe implements PipeTransform {
transform<T = Item>(observer: Observable<T>):
Observable<ObservableStatus<T>> {
return observer.pipe(
map((value: T) => {
return {
loading: false,
error: false,
value,
};
}),
startWith({loading: true}),
catchError(error => of({loading: false, error: true})));
}
}
I want to write unit tests for this functionality using Jasmine. I tried using fakeAsync, delay, tick, flush, discardPeriodicTasks
but it doesn't seem to work.
describe('loading test', () => {
const loadingPipe = new ObservableStatusPipe();
it('returns state of an observable', fakeAsync(() => {
const input: Observable<Item> = of({name: 'Item'}).pipe(delay(1000));
const result: Observable<ObservableStatus<Item>> = loadingPipe.transform(input);
result.subscribe(val => {
expect(val.loading).toEqual(true);
expect(val.value).toBeUndefined();
});
tick(2000);
result.subscribe(val => {
expect(val.loading).toEqual(false);
expect(val.value!.name).toEqual('Item');
});
}));
});
The above test case fails with following failures:
Error: Expected true to equal false. (at expect(val.loading).toEqual(false))
Error: 1 periodic timer(s) still in the queue.
describe('loading test', () => {
const loadingPipe = new ObservableStatusPipe();
it('returns state of an observable', fakeAsync(() => {
const input: Observable<Item> = of({name: 'Item'}).pipe(delay(1000));
const result: Observable<ObservableStatus<Item>> = loadingPipe.transform(input);
result.subscribe(val => {
expect(val.loading).toEqual(true);
expect(val.value).toBeUndefined();
});
tick(2000);
result.subscribe(val => {
expect(val.loading).toEqual(false);
expect(val.value!.name).toEqual('Item');
});
flush(); // <----- here.
}));
});
This is helping to resolve Error: 1 periodic timer(s) still in the queue.
issue. However the test case still fails with:
Error: Expected true to equal false.
TypeError: Cannot read properties of undefined (reading 'name')
Does all this mean the tick
is somehow not simulating time on input
observable?
input
observable directly:describe('loading test', () => {
const loadingPipe = new ObservableStatusPipe();
it('returns state of an observable', fakeAsync(() => {
const input: Observable<Item> = of({name: 'Item'}).pipe(delay(1000));
input.subscribe(val => {
expect(val.name).toBeUndefined();
});
tick(2000);
input.subscribe(val => {
expect(val.name).toEqual('Item');
});
discardPeriodicTasks(); <--- Using flush() here is causing 'Error: 2 periodic timer(s) still in the queue' error.
}));
});
The above test case is passing. But I am still confused why flush() is not working here.
describe('loading test', () => {
const loadingPipe = new ObservableStatusPipe();
it('returns state of an observable', fakeAsync(() => {
const input: Observable<Item> = of({name: 'Item'}).pipe(delay(1000));
const result: Observable<ObservableStatus<Item>> = loadingPipe.transform(input);
result.subscribe(val => {
expect(val.loading).toEqual(true);
expect(val.value).toBeUndefined();
});
tick(2000);
result.subscribe(val => {
expect(val.loading).toEqual(false);
expect(val.value!.name).toEqual('Item');
});
discardPeriodicTasks();
}));
});
This still fails with the same errors:
Error: Expected true to equal false. (at expect(val.loading).toEqual(false))
Error: 1 periodic timer(s) still in the queue.
Can someone explain what is happening here, and how to solve this, please?
Btw, I do not want to use debounceTime
, setTimeOut
to solve this problem. Because they do not seem to simulate time, instead actually waits and delays time i.e. using debounceTime(1000) will actually wait for 1 sec. I do not want that in my case (I want to simulate time).
delay
operator while subscribing the observable is working (without using tick
).describe('loading test', () => {
const loadingPipe = new ObservableStatusPipe();
it('returns state of an observable', fakeAsync(() => {
const input: Observable<Item> = of({name: 'Item'}).pipe(delay(1000));
const result: Observable<ObservableStatus<Item>> = loadingPipe.transform(input);
result.subscribe(val => {
expect(val.loading).toEqual(true);
expect(val.value).toBeUndefined();
});
result.pipe(delay(2000)).subscribe(val => {
expect(val.loading).toEqual(false);
expect(val.value!.name).toEqual('Item');
});
discardPeriodicTasks();
}));
});
Is this actually delaying/waiting for 1000 or 2000ms, or does using fakeAsync somehow let delay to simulate the time?
Thanks!
I think your way 1 is good but you may be facing a situation of late subscribers and multiple subscriptions. Try this:
// Take 2 emissions
result.pipe(take(2)).subscribe(val => {
if (val.loading) {
console.log('False case');
expect(val.loading).toEqual(true);
expect(val.value).toBeUndefined();
} else {
console.log('True case');
expect(val.loading).toEqual(false);
expect(val.value!.name).toEqual('Item');
}
});
// Move time in a fake way. The above observable stream should hopefully emit
// twice and therefore there is a take(2).
// Make sure you see both console.logs to ensure both paths were asserted.
tick(2000);