Search code examples
angularrxjsts-jest

Testing final condition in a method with recursive calls


I have a method in a service that sends post calls recursively, and uses map from rxjs to chain them.

bulkPostRecursive(offset: number, limit: number, ids: string[]): Observable<void> {
    const filteredIds: string[] = ids.slice(offset, offset + limit);
    const end: number = ids.length;
    const data: Record<string, string[]> = {
      ids: filteredIds,
    };

    if (offset + limit < end) {
      return this.bulkPostRecursive(offset + limit, limit, ids).pipe(
        map((): void => {
          this.apiCallService.post('/api_url', data); // this part works as expected
        }),
      );
    } else {
      return this.apiCallService.post('/api_url', data); // this part works as expected
    }
  }

  bulkPost(ids: string[]): Observable<void> {
    const offset: number = 0;
    const limit: number = 2; // value used just for testing purposes
    return this.bulkPostRecursive(offset, limit, ids);
  }

To test the section with this.bulkPostRecursive(offset + limit, limit, idList).pipe, the following test is used:

it('bulkPostRecursive(): should call bulkPostRecursive', () => {
    
    const bulkPostRecursiveSpy = jest.spyOn(service, 'bulkPostRecursive');
    apiCallService.post.mockReturnValue(of({'somevalue': 'somevalue'}));
    service.bulkPostRecursive(0, 4, ['id','id','id,'id']);
    expect(bulkPostRecursiveSpy).toHaveBeenCalled();
    expect(apiCallService.post).toHaveBeenCalled(); // here is where the error occurs

  });

The following error occurs:

expect(jest.fn()).toHaveBeenCalled()

    Expected number of calls: >= 1
    Received number of calls:    0

apiCallService.post is not getting called, please help on how to reach this branch properly.


Solution

  • In you example you are not subscribing to your observable.

    Observable are considered "cold" that means they need a subscriber to execute. In angular the best way to subscribe to an obersable is to use async pipe

     <div>observable$ | async<div>
    

    For post calls its common the need to subscribe in your code.

    observable$.subscribe()
    

    Just be careful with memory leaks doing that way, to prevent that, I normally use one of the options:

    // 1 store the subscription in a value and manually unsubscribe
    subs = observable$.subscribe();
    ngOnDestroy() {
        subs?.unsubscribe();
    }
    
    // 2 make sure to call only once
    observable$.pipe(take(1)).subscribe();
    
    // 3 create an Observable to complete other observables
    // good approach for completing multiple observables
    complete = new Subject();
    observable$.pipe(takeUntil(this.complete)).subscribe();
    observable2$.pipe(takeUntil(this.complete)).subscribe();
    ngOnDestroy() {
        complete.next();
    }