Repository navigation
Open
Labels
P3An issue that is relevant to core functions, but does not impede progress. Important, but not urgentAn issue that is relevant to core functions, but does not impede progress. Important, but not urgentarea: common/httpIssues related to HTTP and HTTP ClientIssues related to HTTP and HTTP Clientfreq1: lowtype: bug/fix
Milestone
Description
I'm submitting a...
[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
[ ] Other... Please describe:
Current behavior
When using async await with promises (and therefore: this.http.get(...).toPromise()), one can unittest everything like normal with the HttpTestingController. BUT if you have a service method that has multiple await in it, the testing controller is not aware of the later executed await and will just wait and fail.
Expected behavior
All await calls inside a method should be executed during async testing.
Minimal reproduction of the problem with instructions
- use a service with async await function like:
@Injectable()
class Service {
constructor(private http: HttpClient){}
public async foobar(): Promise<number> {
await Promise.resolve();
await Promise.resolve();
await this.http.get('http://asdf').toPromise();
return 1;
}
}- try to use the
HttpTestingClientto catch thehttp://asdfrequest.
Environment
Angular version: 6.1.0
Browser:
- [x ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
Reacted by Daniel Hirth, Владимир, Hugh Anderson, Jacob Foshee, Leandro Gehlen, David Dehghan, JanEggers, Danila Chervonny, Matthew Spero, Emanuele Di Saverio and 19 more
Metadata
Metadata
Assignees
Labels
P3An issue that is relevant to core functions, but does not impede progress. Important, but not urgentAn issue that is relevant to core functions, but does not impede progress. Important, but not urgentarea: common/httpIssues related to HTTP and HTTP ClientIssues related to HTTP and HTTP Clientfreq1: lowtype: bug/fix
Activity
leandrogehlen commentedon Oct 9, 2018
My solution for this issue it was to use
setTimeout:dornfeder commentedon Oct 11, 2018
+1
My workaround was to mock the httpClient manually and not inject the service, but create a new one with the mocked httpClient as parameter, but there surely should be a better way of doing this...
joshrizzo commentedon Dec 14, 2018
It works, you just have to make the call and then expect the call, and then await it:
mcgoohan commentedon Nov 6, 2019
This worked for me. The only thing I needed to add was converting it to the promise.
const call = service.login('Test2', 'Password2', 'Local').toPromise();picolino commentedon Nov 12, 2019
Acceptable solution, but I would like to have a simplier solution, like this:
The point is to define answers ahead the requests would be sent.
I think that functionality must be out-of-the-box.
picolino commentedon Feb 19, 2020
Seems like I found an issue that can't be fixed outside.
Let's see on this code:
Both of services calling
httpmethods inside.Now, in the tests we have something like:
So we have behaviour like:
ngOnInitasync method, it going toawait this.someService1.makeSomething1Async();instruction and waiting forhttpMockto flush.await this.someService2.makeSomething2Async();.callfrom 1. instructionhttpMock.expectOne('someService2Url').flush('')will be executed before awaitingsomeService2.makeSomething2Async();. So request does not exists and we already trying to flush it's response.someService2request but error was throwed before on 3.This behaviour can't be fixed anyway without
httpMockresponses collection like I mentioned above without breaking incapsulation or any a-lot-of-code-hacks.thibaud-sanchez commentedon Apr 23, 2020
This solution work if you don't have inner await in the method you are testing.
In my case, If I use your pattern all is working, but If there is another await inside the "service.login('Test2', 'Password2', 'Local')" call to retrieve information from storage for example, then the expectOne fail because http cal was not done yet..
The solution I found, is to add a delay/sleep to make the login able to execute all await before I make the expectOne, like this :
denis111 commentedon Jun 5, 2020
@thibaud-sanchez
Good workaround! But it's not definitive solution. What if we have hundreds of tests like this and so making hundreds delays will make tests execution time more then 100 seconds. Here I tried to play with fakeAsync which works for simple example but didn't work for me for real world code (besides, what if we have variable number of promises before the real http call?) #19974 (comment)
I'd suggest to make possible for httpMock to wait for actual http call with some timeout maybe or something like that
thibaud-sanchez commentedon Jun 5, 2020
Totally agree with you !
kashban commentedon Aug 24, 2020
This is a kinda old issue, any news on solving this without resorting to workarounds?
cameronhudson8 commentedon Jan 1, 2021
I took a look at the source code of the
HttpClientTestingBackendclass that implements theHttpTestingControllerinterface.Any requests that arrive at the testing backend are placed into a private array
open: TestRequest[]. When you call the publicexpectOnefunction, the requests are removed from this array if they are present, or else an error is thrown. The removal is done by thematchfunction, which is also public.So in order to block further test execution until the request has arrived at the
HttpClientTestingBackend, I wanted to wait until there was a request pending in theopenarray. But I could not usematchto check the contents of theopenarray, because upon a successfulmatch, the request is removed from theopenarray, andexpectOnewill subsequently fail. So instead, I had to open up the private array and poll it until it was no longer empty. (I castmockHttp: HttpTestingControllertoanyrather thanHttpClientTestingBackendbecauseHttpClientTestingBackenddoes not appear to be importable).This approach works is comprehensible to those who will follow, but I'm not happy with it. The proper solution would be to implement
expectOneandverifyas @picolino suggested. IMOexpectOneandflushshould be merged into the same function, so that you can expect a request to be made and provide the response that should be sent.verifycould then be used to confirm that all responses were sent.But at least it's now clear to my why the existing package is broken.
RobinMeow commentedon Sep 20, 2024
I have the same issue. I really like the promise based api, so I convert most of my http requests into async functions using firstValueFrom() operator.
A workaround would be:
I dont really like it tho. I most languages you can call async stuff as "fire and forget" and expect it to run eventually. (it might even run synchronously!) So there is no guarentee that the latter will not run first.
In this case it works, because there is no http request sent until it is subscribed to (which will only happen when it is actually awaited)
In another use case, you might need the response from the first request to even create the second request. Often done using
switchMap. Then this workaround would help.