Testing angular service with $httpBackend with async/await using jasmin

Viewed 613

I have previously successfully tested an angular controller that uses ES7 async/await syntax with jasmine -

async updateGridAsync() {
        const paging = angular.copy(this.gridData.getServerCallObj());            
        }
        try {
            const model = await this.service.getAsync(paging);
            this._$rootScope.$apply();
        } catch (e){this._notification.error(this._$rootScope.lang.notifications.unexpectedError);
        }
    }

it('updateGridAsync() should update the gridData when succeed', async (done) => {
    expect(ctrl.gridData.totalItems).toEqual(2);
    expect(ctrl.gridData.items.length).toEqual(2);
    spyOn(service, 'getAsync').and.callFake(() => {
        return Promise.resolve(responseStub);
    });
    await ctrl.updateGridAsync();
    expect(service.getAsync).toHaveBeenCalled();
    expect(ctrl.gridData.totalItems).toEqual(1);
    expect(ctrl.gridData.items.length).toEqual(1);
    done();
});

The above code works perfectly. However, I encountered a problem once trying to test the mocked service code, that calls $http.post. This is the code I run in the service:

async getAsync(pagingData, spinner, gridId, payeeId){            
        const response = await $http.post(url, params);
        const model = this.modelFactory(response.data);
        return model ;
    }

and the test method that isn't able to go a step after the await in updateGridService:

it('getAsync should return correct model', async (done) => {                
    $httpBackend.whenPOST(theUrl).respond(200, responseStub);

    const model = await service.getAsync();

    expect(model.list.length).toEqual(2);        
    $httpBackend.flush();
    done();
});

A few things to point out -

  • Before using async await, this test passed. Now, I don't get passed the await in the service.
  • The functionality works when not in testing context.
  • I'm using jasmine 2.4.1 and angularJS 1.6
2 Answers

I don't think you can use those together - await is actually waiting till the backend promise resolves and that is triggered by flush, but also triggering it before any request has been made does no good.

I mean if you wanna use it in a straightforward way, I personally use it with a helper that creates timeout for the flush action and then awaits for the request.

export async function withHttp(
    callbackHttp: (http: HttpTestingController) => void,
    callbackTest: () => Promise<void>
): Promise<void> {
    const http = TestBed.get(HttpTestingController);
    setTimeout(() => callbackHttp(http));
    await callbackTest();
}

Also don't use async test methods together with done (and don't use done when it's just called on the last line of test, it's not needed then).

Just ran into the same issue and I think there's no solution for it. Maybe upgrading to Angular 7 helps, hopefully the problem is fixed there. But that's not a quick solution.

Awaiting the asynchronous method that does the HTTP calls leads to a deadlock, because the HTTP calls don't return until $httpBackend.flush() is called.

Not awaiting the asynchronous method does not help either. Then the assertions after $httpBackend.flush() can fail because the framework does not wait long enough before continuing with assertions.

I really wonder how this "magic" is implemented. It seems that when the asynchronous method returns either void or IPromise<void> it works. Returning Promise<void> (or using async) does not work though.

Related