Angular HTTP拦截器404请求测试报错:未捕获异常排查
Angular HTTP拦截器404请求测试报错排查
问题现象
编写用于追踪/取消追踪HTTP请求的Angular拦截器时,测试404状态码的请求会出现Uncaught [object Object]错误,尽管拦截器中已经通过catchError处理了错误。
代码片段
拦截器代码
@Injectable() export class TrafficInterceptor implements HttpInterceptor { constructor(private trafficService: TrafficService) { } intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { this.trafficService.trackRequest(request); return next.handle(request).pipe( catchError((error: HttpErrorResponse) => { this.trafficService.unTrackRequest(request); return throwError(error); }), finalize(() => { this.trafficService.unTrackRequest(request); }) ); } }
测试代码
describe('TrafficInterceptor', () => { let interceptor: TrafficInterceptor; let trafficStateStore: TrafficStateStore; let httpTestingController: HttpTestingController; let http: HttpClient; const reqURL = '/somewebsite/foo'; const httpMethods = [ {method: 'get'}, {method: 'delete'}, {method: 'post'}, {method: 'put'}, {method: 'patch'} ]; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [ {provide: HTTP_INTERCEPTORS, useClass: TrafficInterceptor, multi: true}, TrafficInterceptor, TrafficStateStore, TrafficService ] }); interceptor = TestBed.inject(TrafficInterceptor); trafficStateStore = TestBed.inject(TrafficStateStore); httpTestingController = TestBed.inject(HttpTestingController); http = TestBed.inject(HttpClient); }); afterEach(() => { httpTestingController.verify(); }); it('should be created', () => { expect(interceptor).toBeTruthy(); }); httpMethods.forEach(({method}) => { it(`should track and untrack a successfull HTTP - ${method} metod`, () => { httpRequest(reqURL, method); const req = httpTestingController.expectOne(reqURL); expect(trafficStateStore.get('showPendingOverlay')).toBeTrue(); expect(trafficStateStore.get('isRequestProcessing')).toBeTrue(); req.flush({}); expect(trafficStateStore.get('showPendingOverlay')).toBeFalse(); expect(trafficStateStore.get('isRequestProcessing')).toBeFalse(); }); it(`should track and untrack a unsuccessfull HTTP - ${method} metod`, () => { httpRequest(reqURL, method); const req = httpTestingController.expectOne(reqURL); expect(trafficStateStore.get('showPendingOverlay')).toBeTrue(); expect(trafficStateStore.get('isRequestProcessing')).toBeTrue(); req.flush('', {status: 404, statusText: 'error'}); expect(trafficStateStore.get('showPendingOverlay')).toBeFalse(); expect(trafficStateStore.get('isRequestProcessing')).toBeFalse(); }); }); function httpRequest(url: string, method: string, data?: Record<string, boolean>, params: Record<string, boolean> = {}): void { if (data) { http[method](url, data, {params}).subscribe(); } else { http[method](url, {params}).subscribe(); } } });
问题原因
测试中的httpRequest函数调用subscribe()时,仅使用无参数的订阅方式,未处理错误回调。当请求返回404错误时,拦截器的catchError会重新抛出错误,而该错误未被订阅的错误处理器捕获,导致测试环境抛出未捕获的错误。
另外,拦截器中catchError和finalize都调用了unTrackRequest,会导致错误场景下该方法被调用两次,这是潜在逻辑问题,但不是当前报错的直接原因。
解决方案
1. 为订阅添加错误处理
修改httpRequest函数中的订阅逻辑,添加错误回调以捕获抛出的错误:
function httpRequest(url: string, method: string, data?: Record<string, boolean>, params: Record<string, boolean> = {}): void { if (data) { http[method](url, data, {params}).subscribe({ next: () => {}, error: () => {} }); } else { http[method](url, {params}).subscribe({ next: () => {}, error: () => {} }); } }
2. 修复拦截器重复调用unTrackRequest的问题
finalize操作符无论请求成功还是失败都会执行,因此可以移除catchError中的unTrackRequest调用,只保留finalize中的调用,避免重复执行:
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { this.trafficService.trackRequest(request); return next.handle(request).pipe( catchError((error: HttpErrorResponse) => { return throwError(error); }), finalize(() => { this.trafficService.unTrackRequest(request); }) ); }
以上修改既解决了未捕获错误的问题,也优化了拦截器的逻辑一致性。
内容的提问来源于stack exchange,提问作者Bojidari
相关产品推荐
相关产品推荐

