You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular HTTP拦截器单元测试:如何监视getNewTokenAndRetryRequest方法?

Angular HTTP拦截器单元测试:无法监视getNewTokenAndRetryRequest方法

我正在对用于授权的Angular HTTP拦截器进行单元测试,已覆盖基础成功场景,现在要测试访问令牌过期导致API调用失败的场景。但运行测试时出现错误:Error: Expected spy getNewTokenAndRetryRequest to have been called.,求正确监视AuthInterceptorService的getNewTokenAndRetryRequest()方法的方案。

我的单元测试代码

beforeEach(() => {
  TestBed.configureTestingModule({
    imports: [
      HttpClientTestingModule
    ],
    providers: [TokenStorageService,
  
    {
        provide: HTTP_INTERCEPTORS,
        useClass: AuthInterceptorService,
        multi: true
    }      
    
    ]

  });
  service = TestBed.inject(AuthInterceptorService);
});


it('should proceed to refresh token logic on 500 error', inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
    let  thownError: Error = null;

    spyOn(service, "getNewTokenAndRetryRequest").and.stub();


    Object.defineProperty(window.document, 'cookie', {
      writable: true,
      value: 'access_token=mycookie',
    });

    http.get('/data').subscribe(
        response => {
            expect(response).toBeTruthy();
        }
    );


    const req = httpMock.expectOne(r =>
        r.headers.has('Authorization') && 
        r.headers.get('Authorization').startsWith('Bearer ewogIC') );

    req.flush("failure",{ status: 500, statusText: "500 error" });

    expect(service.getNewTokenAndRetryRequest).toHaveBeenCalled();

    httpMock.verify();
  }));

我的拦截器代码

export class AuthInterceptorService implements HttpInterceptor {

  constructor(private tokenStorageService: TokenStorageService, private httpClient: HttpClient) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
    const updatedHeaders:HttpHeaders = req.headers.set('Authorization', 'Bearer ' + this.tokenStorageService.getAccessToken());
    const updatedRequest = req.clone({headers: updatedHeaders})
    return next.handle(updatedRequest)
    .pipe(  
      catchError((error: HttpErrorResponse) => {
        if (error.status===500) {

          console.log('500 error, try to use the refresh token');
          try{
            this.getNewTokenAndRetryRequest(req);
            return of([]);
          }catch (error) {
            return throwError(error);
          }
        }else{
          let errorMsg =  `Error Code: ${error.status},  Message: ${error.message}`;

          return throwError(error.error);
        }
       
    })
   )
  }

  getNewTokenAndRetryRequest(req: HttpRequest<any>): void {
    //do stuff

  }
 
}

问题分析与解决方法

1. 拦截器实例不匹配

你通过TestBed.inject(AuthInterceptorService)获取的实例,和Angular拦截器链中实际运行的实例并非同一个。因为用useClass提供拦截器时,Angular会创建新实例,导致你监视的实例从未被调用。

**解决:**手动创建拦截器实例并通过useValue提供,确保测试使用同一个实例:

beforeEach(() => {
  // 手动创建拦截器实例
  const authInterceptor = new AuthInterceptorService(
    TestBed.inject(TokenStorageService),
    TestBed.inject(HttpClient)
  );
  TestBed.configureTestingModule({
    imports: [HttpClientTestingModule],
    providers: [
      TokenStorageService,
      {
        provide: HTTP_INTERCEPTORS,
        useValue: authInterceptor, // 替换useClass为useValue
        multi: true
      }
    ]
  });
  service = authInterceptor; // 直接使用该实例
});

2. 请求匹配条件错误

你的expectOne要求请求头以Bearer ewogIC开头,但实际头是Bearer 加上tokenStorageService.getAccessToken()的返回值。如果getAccessToken()返回的不是ewogIC...,就会匹配失败,后续的req.flush不会执行,拦截器的错误分支也不会触发。

解决:

  • 简化匹配条件,只检查Authorization头存在:
const req = httpMock.expectOne(r => r.headers.has('Authorization'));
  • 或者Mock TokenStorageService的getAccessToken方法,返回你期望的字符串:
const tokenStorage = TestBed.inject(TokenStorageService);
spyOn(tokenStorage, 'getAccessToken').and.returnValue('ewogIC...');

3. 异步操作未等待

拦截器的错误处理是异步的,直接断言可能会在操作完成前执行,导致断言失败。

**解决:**使用fakeAsync和tick()等待异步操作完成:

it('should proceed to refresh token logic on 500 error', fakeAsync(inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
  spyOn(service, "getNewTokenAndRetryRequest").and.stub();

  const tokenStorage = TestBed.inject(TokenStorageService);
  spyOn(tokenStorage, 'getAccessToken').and.returnValue('mycookie');

  http.get('/data').subscribe(
    () => {},
    () => {}
  );

  const req = httpMock.expectOne(r => r.headers.has('Authorization'));
  req.flush("failure",{ status: 500, statusText: "500 error" });

  tick(); // 触发所有异步流程

  expect(service.getNewTokenAndRetryRequest).toHaveBeenCalled();
  httpMock.verify();
})));

4. 订阅回调覆盖不完整

你的subscribe只处理了成功回调,虽然拦截器在500错误时返回of([])会触发成功回调,但最好补充错误回调避免测试潜在报错。

内容的提问来源于stack exchange,提问作者Mr Smith

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 01:20:46