-
Notifications
You must be signed in to change notification settings - Fork 26.2k
fix(testability): fix chained http call #20924
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Fixes an issue where chained http calls would prematurely call testability whenStable callbacks after the first http call. Fixes #20921
This was reverted by 47b7898 because it failed internal google tests https://sponge.corp.google.com/a1c7ae5d-bbdd-4493-a424-95b95fabfb8c |
Since I can't see what the failed internal tests at google are, is there any input required from me to get this sorted? |
|
// uses a 0 second timeout to check for outstanding tasks we add our 0 second timeout after a | ||
// micro task (which ensures Testability's timeout is run first). | ||
function afterTestabilityCheck(fn: Function): void { | ||
scheduleMicroTask(() => setTimeout(fn)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I bet some tests rely on the fact that it executes in next microTask rather than MacroTask (setTimeout
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, well the base problem is that waiting for next microTask is not enough for specifically the situation with chained http calls.
Presumably there is an alternative fix which instead modifies the XHRBackend or http/httpClient in some manner, perhaps to use the already existing increasePendingRequestCount infrastructure. However the methods are not part of the public testability interface and in the angular codebase these methods are only used in the testability_spec. Which suggests they are intended to be used for testing.
Which direction do you believe would be the right way to move forward? How common are these failing internal tests? Presumably only things which tests testability should care about how testability determines that it is stable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alxhub Can you look into this and recommend direction?
Any updates on this? Without this fix Protractor e2e tests with chained http calls fails. And we can't update our app past Angular 5.2.1 |
@mhevery , @lyml , I would like to look into this one. And I believe the current behavior (before the PR) is correct, doChainedHttpCalls() {
this.initCalls();
this.http
.get('https://httpbin.org/delay/1')
.switchMap(() => {
this.state = State.afterFirst;
return this.http.get('https://httpbin.org/delay/1');
})
.subscribe(() => {
this.state = State.afterSecond;
});
this.zone.runOutsideAngular(() =>
this.testability.whenStable(() =>
this.setStable()));
}
doChainedTimeout() {
this.initCalls();
Observable.timer(1000)
.switchMap(() => {
this.state = State.afterFirst;
return Observable.timer(1000);
})
.subscribe(() => {
this.state = State.afterSecond;
});
this.zone.runOutsideAngular(() => this.testability.whenStable(() => this.setStable()));
}
behave differently , because
I think this is the correct behavior, if we need to consider the chained http call as |
I think chained http calls is a very common practice, that can't be avoided without rewrite of apps and API they use. Rich use of RXJS in Angular apps promotes huge usage of different concatMap/mergeMap etc calls. Don't waiting on them to complete is nonsense. If we think about timers I suppose we should wait for timers to complete, its totally OK. If timers don't complete they should be excluded from test environment. |
@quanterion , I think we should provide some other helper method to let |
@JiaLiPassion As you can see here waitForAngular() in protractor depends on whenStable() And let us think from definition point of view. Is whenStable() should be true when only first wave of pending tasks are finished? Is component really becomes stable after that? Is zone becomes stable if it should immediately run another task? Is view is stable from user point of view if no data is rendered? From developer point of view you make rich composition of RxJs streams and calls subscribe only once. And It looks weird that whenStable() returns true before subscribe fires and no data is rendered yet. I do think whenStable() should be a common way to test components with very popular RxJS stream orchestration. May be there should be another function to wait specially for the first wave of microtasks? I.e. drainCurrentMacrotasks that waits only for tasks currently in queue and clearly indicates that it doesn't wait for anything else. |
@JiaLiPassion You are correct in describing why it doesn't work but it isn't an argument for why it shouldn't work. The only purpose of the testability api is to tell Protractor when there are no longer any pending asynchronous tasks. That is its only reason for existing. Nothing inside of angular actually consumes Testability so adding a new testability, let's call it Testability2, would then lead to Testability1 not being consumed. It would also lead to Protractor having to update how they communicate with Angular due to an internal Angular bug. This pull request does not modify NgZone. It modifies how Testability determines that all pending tasks have been completed. This is needed because Testability does not handle the case of chained http calls correctly, something that is clearly unintended. |
@lyml , @quanterion , yes, I agree with you guys that What I want to discuss is should we consider such case (such as chained http) to be equals to
So for
so for the case here. this.http
.get('https://httpbin.org/delay/1')
.switchMap(() => {
this.state = State.afterFirst;
return this.http.get('https://httpbin.org/delay/1');
})
.subscribe(() => {
this.state = State.afterSecond;
}); The sequence is
it is really So I think maybe we should have some other |
@JiaLiPassion Your considerations makes a lot of sense but I think sequence is not correct.
The sequence is
From this point of view the zone is really stable not twice, but three times. There should be no difference between 1st http call sheduled and 2nd http call sheduled |
@JiaLiPassion Nobody is talking about modifying NgZone. It is the Testability implementation that is being fixed. There are two different APIs here which has a stable event. NgZone.onStable an observable which should fire if there are no pending microtasks (does not care about macrotasks at all). Testability.whenStable which takes a callback that should fire when there are no more pending asynchronous tasks. NgZone.onStable will fire multiple times before Testability.whenStable fires. In the current implementation Testability.whenStable schedules a microtask after each NgZone.onStable event, it uses this microtask to check if all pending macro tasks are completed and in that case it incorrectly marks the environment as stable. However, for pending http requests it is not enough to wait for a microtask. Therefore the testability implementation is not working properly (which leads to the bugs reported in Protractor). |
@lyml ,
Thank you for explanation, you are right, I mixed up the So I may misunderstanding the issue. in my understanding, with the code this.http
.get('https://httpbin.org/delay/1')
.switchMap(() => {
console.log('after first isStable', this.zone.hasPendingMacrotasks, this.zone.hasPendingMicrotasks);
this.state = State.afterFirst;
return this.http.get('https://httpbin.org/delay/1');
})
.subscribe(() => {
console.log('after second isStable', this.zone.hasPendingMacrotasks, this.zone.hasPendingMicrotasks);
this.state = State.afterSecond;
}); So about this case, after first http call, there is no pending And the issue this PR want to resolve is the |
Fixes an issue where chained http calls would prematurely call testability whenStable callbacks after the first http call. Fixes angular#20921 PR Close angular#20924
This reverts commit 7e3f9a4.
Any updates on this issue? |
Hi @lyml! This PR has merge conflicts due to recent upstream merges. |
@lyml , I believe for this case, the underlying reason of the
I have created a fork based on your repo, https://github.com/JiaLiPassion/chained-http, with my @mhevery, please review, thank you! |
Fixes an issue where chained http calls would prematurely call testability whenStable callbacks after the first http call. Fixes angular#20921 PR Close angular#20924
This reverts commit 7e3f9a4.
@JiaLiPassion - do your fixes to zone.js mean that this PR is no longer needed? |
@petebacondarwin , yes, this has been fixed in the zone.js repo, so this one can be closed. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Fixes an issue where chained http calls would prematurely call
testability whenStable callbacks after the first http call.
Fixes #209201
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Testability will call the methods queued up with whenStable asynchronously if the macro task queue is empty after an onStable event has fired from ngZone.
This results in the problem described in #20921
What is the new behavior?
Testability will queue an asynchronous check that the macro test queue is empty and then empty the whenStable queue,
Does this PR introduce a breaking change?
Other information
The reference issue contains a test which is solved with the code fixes, however it is a protractor test dependent on http calls actually being made, currently working on expressing the intended behaviour as a unit test.
Furthermore the change introduces a timer which tests running fakeAsync do not take into consideration, therefore these tests fail. Currently working on disabling testability for those unit tests.