Angular拦截器多请求顺序测试异常问题排查
Let's break down why your test is failing and what's happening under the hood:
The Root Cause of the Order Mismatch
Your interceptor's logic creates a subtle execution order issue when multiple requests hit a token refresh at the same time:
- First request (req1) triggers refresh: When
req1enters the interceptor,isRefreshingisfalse, so it kicks off therefreshTokencall and waits for it to complete. - Subsequent requests (req2, req3) wait on the subject: While
refreshTokenis delayed (1 second in your test),req2andreq3come in. SinceisRefreshingis nowtrue, they subscribe torefreshTokenSubjectand hold on for the new token. - Token refresh completes, triggering subscribers first: When
refreshTokenfinally resolves, the code runs:this.refreshTokenSubject.next(token): This is a synchronous operation that immediately triggers all active subscribers (req2 and req3). Theirnext.handle()calls fire right away.- Only after that does the code execute
return next.handle(request)forreq1.
This is why your test sees the order req2 → req3 → req1 instead of your expected req1 → req2 → req3.
Why It Seems to Work in the Real World
In your actual web interface, requests are rarely fired synchronously one after another like in your test. There's usually natural delay between user actions or API triggers, so you never hit the scenario where three requests are all waiting on a token refresh at the same time. The order issue only surfaces in your controlled test environment.
How to Fix It
You have two options depending on your goal:
1. Fix the Interceptor to Preserve Request Order
If you want the interceptor to guarantee that requests are processed in the order they were triggered, adjust the logic to have all requests (including the first one) wait on the refreshTokenSubject:
private handleTokenRefresh(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { if (!this.isRefreshing) { this.isRefreshing = true; this.refreshTokenSubject.next(null); // Fire off the refresh token call and update the subject when done this.jwtService.refreshToken().pipe( finalize(() => this.isRefreshing = false), catchError(err => { this.isRefreshing = false; return throwError(() => err); }) ).subscribe(token => { this.refreshTokenSubject.next(token); }); } // All requests wait on the subject, so they execute in subscription order return this.refreshTokenSubject.pipe( filter(token => token !== null), take(1), switchMap(jwt => next.handle(request)) ); }
With this change, req1 will subscribe to the subject first, then req2, then req3. When the token is emitted, the subject will notify subscribers in the order they subscribed, preserving your desired execution sequence.
2. Adjust the Test to Match Current Interceptor Behavior
If you don't need to preserve request order and just want the test to pass, update your assertions to match the actual execution order:
expect(httpHandlerSpy.handle.calls.argsFor(0)).toEqual([httpRequestMock2]); expect(httpHandlerSpy.handle.calls.argsFor(1)).toEqual([httpRequestMock3]); expect(httpHandlerSpy.handle.calls.argsFor(2)).toEqual([httpRequestMock1]);
内容的提问来源于stack exchange,提问作者Majesty

