如何用Jest测试Angular中带重试策略的HttpInterceptor
解决HttpErrorInterceptor重试逻辑的单元测试问题
一、修正HttpTestingController测试的TypeError问题
TypeError大多是因为重试过程中发起了多次HTTP请求,而你误用了仅匹配单次请求的expectOne,导致后续重试请求找不到匹配项抛出错误。正确做法是根据重试次数,多次调用expectOne并依次处理响应;如果重试有延迟,需用fakeAsync+tick模拟时间流逝。
测试1:所有重试返回500,最终返回错误
import { TestBed } from '@angular/core/testing'; import { HttpClient, HTTP_INTERCEPTORS } from '@angular/common/http'; import { HttpErrorInterceptor } from './http-error.interceptor'; import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; describe('HttpErrorInterceptor', () => { let httpClient: HttpClient; let httpTestingController: HttpTestingController; const RETRY_TIMES = 3; // 对应你的重试策略次数 beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [ { provide: HTTP_INTERCEPTORS, useClass: HttpErrorInterceptor, multi: true } ] }); httpClient = TestBed.inject(HttpClient); httpTestingController = TestBed.inject(HttpTestingController); }); afterEach(() => { httpTestingController.verify(); }); it('should return error when all retries fail with 500', (done) => { httpClient.get('/test').subscribe({ next: () => fail('Expected error, not success'), error: (err) => { expect(err.status).toBe(500); done(); } }); // 处理初始请求+所有重试请求 for (let i = 0; i <= RETRY_TIMES; i++) { const req = httpTestingController.expectOne('/test'); req.flush('Server error', { status: 500, statusText: 'Internal Server Error' }); } });
测试2:某次重试成功,返回结果
it('should return success when retry succeeds', (done) => { httpClient.get('/test').subscribe({ next: (res) => { expect(res).toBe('Success'); done(); }, error: () => fail('Expected success, not error') }); // 前两次返回500,第三次返回成功(对应重试2次) const firstReq = httpTestingController.expectOne('/test'); firstReq.flush('Error', { status: 500, statusText: 'Internal Server Error' }); const secondReq = httpTestingController.expectOne('/test'); secondReq.flush('Error', { status: 500, statusText: 'Internal Server Error' }); const thirdReq = httpTestingController.expectOne('/test'); thirdReq.flush('Success', { status: 200, statusText: 'OK' }); }); });
如果重试策略包含延迟,需用fakeAsync模拟时间:
it('should handle retries with delay', fakeAsync(() => { let errorReceived = false; httpClient.get('/test').subscribe({ error: () => errorReceived = true }); for (let i = 0; i <= RETRY_TIMES; i++) { const req = httpTestingController.expectOne('/test'); req.flush('Error', { status: 500 }); tick(1000); // 匹配你的重试间隔时间 } expect(errorReceived).toBe(true); }));
二、修复RxJS Marble测试重试未触发的问题
Marble测试必须用TestScheduler控制RxJS时间调度,确保重试的延迟逻辑被正确模拟,所有流操作要放在TestScheduler.run回调中执行。
假设你的重试策略基于retryWhen实现,对应的Marble测试代码:
import { TestScheduler } from 'rxjs/testing'; import { HttpErrorInterceptor } from './http-error.interceptor'; import { HttpRequest, HttpHandler, HttpResponse, HttpErrorResponse } from '@angular/common/http'; import { of, throwError } from 'rxjs'; describe('HttpErrorInterceptor (Marble Tests)', () => { let testScheduler: TestScheduler; let interceptor: HttpErrorInterceptor; let httpHandler: jasmine.SpyObj<HttpHandler>; beforeEach(() => { testScheduler = new TestScheduler((actual, expected) => { expect(actual).toEqual(expected); }); interceptor = new HttpErrorInterceptor(); httpHandler = jasmine.createSpyObj('HttpHandler', ['handle']); }); it('should return error when all retries fail', () => { testScheduler.run(({ expectObservable }) => { const request = new HttpRequest('GET', '/test'); // 模拟3次请求都返回500 httpHandler.handle.and.returnValues( throwError(() => new HttpErrorResponse({ status: 500 })), throwError(() => new HttpErrorResponse({ status: 500 })), throwError(() => new HttpErrorResponse({ status: 500 })) ); const result$ = interceptor.intercept(request, httpHandler); // ---# 表示延迟后抛出错误(#代表错误事件) expectObservable(result$).toBe('---#', undefined, new HttpErrorResponse({ status: 500 })); }); }); it('should return success when retry succeeds', () => { testScheduler.run(({ expectObservable }) => { const request = new HttpRequest('GET', '/test'); // 前两次返回500,第三次返回成功 httpHandler.handle.and.returnValues( throwError(() => new HttpErrorResponse({ status: 500 })), throwError(() => new HttpErrorResponse({ status: 500 })), of(new HttpResponse({ body: 'Success', status: 200 })) ); const result$ = interceptor.intercept(request, httpHandler); // -------x 表示经过两次延迟后发出成功值(x代表next事件) expectObservable(result$).toBe('-------x', { x: 'Success' }); }); }); });
关键注意事项
- 所有流操作必须包裹在
TestScheduler.run中,确保时间被调度器管控。 - Marble字符串的长度要对应重试延迟时间(比如每个
-代表10ms,1秒延迟对应100-)。 httpHandler.handle的调用次数要和重试次数匹配,每次返回对应的错误或成功响应。
内容的提问来源于stack exchange,提问作者MauriceDev
相关产品推荐
相关产品推荐

