jasmineangular-unit-test

Shouldn't fakeAsync prevent automatic subscription completion?


I thought that by using a fakeAsync wrapper, my tests wouldn't automatically run subscriptions and that I'd be controlling that part by calling tick manually, but that doesn't seem to be the case. For example, using this method:

foo(): void {
    of([1, 2, 3]).subscribe({
        next: () => {
            console.info('Subscription completed.')
            this.value = true
        },
        error: (err: unknown) => console.error('error called')
    })
}

and testing with this spec:

it('should foo', fakeAsync(() => {
    component.foo()
    expect(component.value).toBeFalse()
}))

I'm seeing the subscription completed message print and thus the expectation fails. I thought that the foo method would be called, but that the subscription wouldn't complete until I put a tick() call into the spec.

What am I doing wrong?


Solution

  • Your assumption that all observables are asynchronous is wrong.

    Observables can either be asynchronous or synchronous depending on the underlying implementation. In other words, reactive streams are synchronous if the source is synchronous unless you explicitly make them asynchronous.

    Some examples:

    //Synchronous
    of(1,2)
      .subscribe(console.log);
    
    //asynchronous because of async source
    interval(1000)
      .subscribe(console.log);
    
    //aynchronous because one stream is async (interval)
    of(1,2)
      .pipe(
        mergeMap(x => interval(1000).pipe(take(2)))
      )
      .subscribe(console.log);
    
    //async because we make it async
    of(1,2, asyncScheduler)
      .subscribe(console.log);
    

    TLDR: Because of([1, 2, 3]) is a synchronous source, you won't be able to control when it completes with fakeAsync.