Search code examples
angularjasminekarma-jasmineangular-httpclient

How can I test recursive http requests that have a delay?


I have a somewhat complex situation where, do to backend implementation, I need to poll the backend until either

  1. I get a success status from the BE
  2. I get a fail status from the BE
  3. I get onGoing for longer than my retry count (client side TO)

The code works, and testing against the backend succeeded in all three situations. I have also succeeded in testing the first and second situations, if the result is instantaneous (mocking the response of complete / fail immediately, without getting onGoing in the middle). However, I have not been able to test the third case. I got different errors with each attempt, so now I am turning to the collective mind of S.O. for some help :-)

ts file:

doAction(content): Observable<resPassType> {
    return this.postAction(content) // private function that just calls HTTP Post at url1, and returns an actionId for polling
      .pipe(concatMap(action => this.poll(action))); // see below
}

private poll(action, retryCount = config.retryCount): Observable<resPassType>{
    return time(config.retryTime).pipe(switchMap(() =>
      this.getActionStatus(action) // private function that calls HTTP get at url2/action.id
      .pipe(concatMap ( res=> {
        retryCount--;
        if(retryCount < 0)
            return throwError('Action Timed Out'); // this is essentially what I want to test
        switch(res.action.state.toLowerCase()) {
            case 'completed':
                return of(resPassTypeData);//taken from within res.action. Successfully tested
            case 'failed':
                return throwError('Action Failed'); // successfully tested
            case 'ongoing':
                return this.poll(res.action, retryCount)
            default:
                return throwError('unexpeceted action state');
            }
        }))));
}

Seeing the successful tests may help you help me, so included here is the test for failure

it('should throw error if action failed', fakeAsync(()=>{
    const mockGetActionResponse = {'action' : {'id' : mockActionId, state: 'Failed' }};
    
    service.doAction(mockContent).subscribe(
        () => {},
        err => expect(err).toEqual('Action Failed');
    );

    const postCall = httpTestingController.expectOne(url1);
    expect(postCall.request.method).toEqual('POST');
    expect(postCall.request.body).toEqual(mockContent);

    postCall.flush(mockPostActionResponse);

    tick(config.retryTime);

    const getCall = httpTestingController.expectOne(url2/mockActionId);
    expect(getCall .request.method).toEqual('Get');

    getCall.flush(mockGetActionResponse );
}));

This test works. How can I write the test for case 3? Writing the same test and changing the state to onGoing and changing the error expectation left me with the error

error expected no open requests found 1

My hunch is that is has something to do with the recursion or the delay or something, since this is the only test case where poll gets called more than once. Any leads would be greatly appreciated :-)

EDIT Code has been updated thanks to comments and improvements from @Andrei. Issue still persists.


Solution

  • Answer found by @Andrei in the comments.

    Working code now looks like this:

    it('should throw error if action timed out',()=>{
        const mockGetActionResponse = {'action' : {'id' : mockActionId, state: 'onGoing' }};
    
        service.doAction(mockContent).subscribe(
            () => {},
            err => expect(err).toEqual('Action Timed Out');
        );
    
        const postCall = httpTestingController.expectOne(url1);
        expect(postCall.request.method).toEqual('POST');
        expect(postCall.request.body).toEqual(mockContent);
    
        postCall.flush(mockPostActionResponse);
    
        for( let i=0; i<config.retryCount; i++) {
            tick(config.delayTime);
            const getCall = httpTestingController.expectOne(url2);
            expect(getCall.request.method).toEqual('GET');
            getCall.flush(mockGetActionResponse)
        }
    })