in my Angular service I have some code that performs polling to show a spinner until a certain condition is met:
@Inject({…})
export class SomeService {
backendCall(): Observable<SomeStatusWrapper> {
return this.http.get(…)
}
isDocumentReady(): Observable<boolean> {
const hasFinished = new Subject<boolean>()
return interval(1000).pipe(
switchMap(idx => of(idx).pipe(delay(idx*250))), // Increment delay between retries
switchMap(() => this.backendCall()),
map(({status}) => status === 'Done'),
tap(done => done?hasFinished.next(done) : undefined),
distinctUntilChanged(),
takeUntil(hasFinished),
endWith(true)
)
}
}
Now, I'd like to add a unit test for this one and I am using Jasmine marbles, but I am not certain of how many frames there's going to be there and I can't get to get the unit test working.
How does one express an 'interval' with marble syntax? I know we can do something like cold('1000ms (a|)',{a:true}) where it waits 1000ms, emits "true" and completes at the same time.
I was assuming that this test would pass but it failed:
it('should emit false after 1000ms', () => {
const service = TestBed.inject(service) // already mocked backendCall to return a status != 'Done'
expect(service.isDocumentReady()).toBeObservable(cold('1000ms f', {f: false}))
})
Simpler version
How do I write jasmine marbles to make this test pass?
describe('', () => {
it('should pass', () => {
const stream$ = interval(1000).pipe(
switchMap(val => of(val).pipe(delay(250*val))),
mapTo(false)
)
expect(stream$).toBeObservable(cold(???, {f:false}))
})
})
I think the problem is that the call to
toBeObservablesubscribes and asserts at the same time, but time needs to be advanced before the assertion is made. This assumption is based on debugging jasmine and jasmine-marbles to try and understand what is really happening under the hood.Using the test scheduler, it is possible to reorganise the code so that the subscription starts and the assertion is made afterwards:
expectObservable(partner$).toBe('500ms (a|)', {a})I have an example of something similar working in an Angular app. There is a service which has the following methods which return observables:
The following test passes:
Compare it to the following tests, structured like the one from the OP, which don't work. The comments attempt to explain why.
The following test manually subscribes, and helps to explain why the above tests fail: