-->

Delaying a response with $httpBackend

2019-07-19 11:48发布

问题:

In my view I have loading animation that is displayed until I receive a response from the API.

//Displayed before we've received API response
<p ng-if="vm.vehicles == null">Loading ...</p>

//Displayed once we received response for the API
<table ng-if="vm.vehicles">
    <tr ng-repeat="vehicle.vm.vehicles">...</tr>

To do my testing, I use the $httpBackend Angular module. Something like this:

$httpBackend.whenGET('api/vehicles').respond({vehicles: [...]});

Problem

I want to write a test to check the the loading animation is displayed.

I tried:

expect(ptor.isElementPresent(By.cssContainingText('p', 'Loading'))).to.eventually.be.true;`

but i does't pass. So i think I need to conditionally delay the response I get from $httpBackend.

I found this blog post http://endlessindirection.wordpress.com/2013/05/18/angularjs-delay-response-from-httpbackend/

However, inserting that config method made all my tests fails (I think because the templates aren't loaded in time), it delays ALL responses so it's not really what I need.

So how can I delay the response for that one call? Ideally, I would like to delay it only for that test.

回答1:

Look at the documentation of $http, in particular interceptors.

This is the interesting bit:

response: interceptors get called with http response object. The function is free to modify the response object or create a new one. The function needs to return the response object directly, or as a promise containing the response or a new response object.

In other words, you could intercept the response and rather than return it right away, return a promise that would return the response after a timeout...

e.g. plunkr example.

But in the case of end-to-end testing, you have to deal with an $httpBackend object.

This bit comes into play:

As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application is being developed with the real backend api replaced with a mock, it is often desirable for certain category of requests to bypass the mock and issue a real http request (e.g. to fetch templates or static files from the webserver). To configure the backend with this behavior use the passThrough request handler of when instead of respond.

Translation: you could use .passThrough() and your interceptors should work:

$httpBackend.whenGET('api/vehicles').passThrough();

The drawback is that this would do the actual request.

You can also check this question which is very similar: AngularJS - Using ngMockE2E $httpBackend how can I delay a specific response?



回答2:

I was able to get this to work by using a timeout to resolve a promise inside the function available on the respond() method of $httpBackend.

I was testing a method on an angular service of mine that populates a list with the results from an API call. Additionally, I have bools on that service that indicate when the list is loading and when it is finished. So in my particular unit test I wanted simply to make sure the "loading" bool was set to true and then back to false when finished (and that the "finished loading" bool was set to true upon successful filling of that list.

To do this, I needed to delay my mocked httpBackend GET response, allowing me to check that the "loading" bool was set to true and the "finished loading" bool was undefined, but then after the timeout elapsed both bools needed to have new values.

UPDATE: Apologies for not updating this sooner. I was putting a $timeout inside the respond() before, however because I wasn't performing a $timeout.flush() the expect statements inside the $timeout function were not executing. I don't simulate delays that way anymore, but I forgot I had this posted here. Thanks @Mark-Amery for bringing to my attention.

var fakeList = [{ id: 1, name: "item 1" }, { id: 2, name: "item 2" }];

$http.expectGET('/myapi/myresource').respond(fakeList);

 myAngularService.myMethod();

 expect(myAngularService.listIsLoading).toBeTruthy();
 expect(myAngularService.listFinishedSuccessfully).toBeUndefined();

 $http.flush();

 expect(myAngularService.listIsLoading).toBeFalsy();
 expect(myAngularService.listFinishedSuccessfully).toBeTruthy();
 expect(myAngularService.myList).toEqual(fakeList);