How to test an Angular interceptor with a retry strategy?

Discussão em 'Angular' iniciado por Chris Barr, Janeiro 13, 2025.

  1. Chris Barr

    Chris Barr Guest

    This is a simplified version of my Angular 18 error interceptor. It will retry requests that produced an error to make sure it was not a fluke (please don't ask!). This works, however I am running into issues trying to write unit tests for this

    let apiService: ApiService;

    const genericRetryStrategy = (): ((e: HttpErrorResponse, i: number) => Observable<number>) => {
    const maxRetryAttempts = 1;
    const scalingDuration = 200;
    const excludedStatusCodes = [401, 403, 503];
    return (error: HttpErrorResponse, i: number): Observable<number> => {
    const retryAttempt = i + 1;
    if (retryAttempt > maxRetryAttempts || excludedStatusCodes.some((code) => code === error.status)) {
    return throwError(() => error);
    }
    console.info(`Attempt ${retryAttempt.toString()}: retrying in ${(retryAttempt * scalingDuration).toString()}ms`);
    // retry after 1s, 2s, etc...
    return timer(retryAttempt * scalingDuration);
    };
    };

    const catchAll$ = (code: string, url: string, error: HttpErrorResponse): Observable<never> => {
    console.error('Logging Error', code, error.message);
    apiService.logError$(msg, error.error, url).subscribe({
    error: () => {
    console.error('Unable to log error!');
    },
    });

    return throwError(() => new Error(msg));
    };

    export const httpErrorInterceptor: HttpInterceptorFn = (req, next) => {
    apiService = inject(ApiService);

    return next(req).pipe(
    retry({ delay: genericRetryStrategy() }),
    catchError((error: HttpErrorResponse) => {
    return catchAll$(code, req.url, error);
    }),
    );
    };


    I am using the jasmine-auto-spies package (which is great!) - but I am not sure exactly how to test this logic. Here is what I have so far

    const interceptor: HttpInterceptorFn = (req, next) => TestBed.runInInjectionContext(() => httpErrorInterceptor(req, next));
    let httpClient: HttpClient;
    let httpMock: HttpTestingController;

    beforeEach(() => {
    TestBed.configureTestingModule({
    imports: [],
    providers: [
    provideHttpClient(withInterceptors([interceptor])),
    provideHttpClientTesting(),
    {
    provide: ApiService,
    useValue: createSpyFromClass(ApiService, {
    methodsToSpyOn: ['logError$'],
    }),
    },
    ],
    });

    httpClient = TestBed.inject(HttpClient);
    httpMock = TestBed.inject(HttpTestingController);
    });

    afterEach(() => {
    // Assert that no other requests were made after each test completes
    httpMock.verify();
    });

    describe('Error Retrying', () => {
    it('should retry a 500 error only once with a slight delay', () => {
    httpClient.get('http://example.com/api/whatever').subscribe();
    const req1 = httpMock.expectOne('http://server.biz/api/whatever');
    req1.flush('message 1 here', { status: 500, statusText: 'Server Error 1' });
    const req2 = httpMock.expectOne('http://server.biz/api/whatever');
    req2.flush('message 2 here', { status: 500, statusText: 'Server Error 2' });

    // ???
    //How do I verify this was called only twice?
    });

    it('should NOT retry a 401 error', () => {
    httpClient.get('http://example.com/api/whatever').subscribe();
    const req1 = httpMock.expectOne('http://server.biz/api/whatever');
    req1.flush('message here', { status: 401, statusText: 'Unauthorized' });

    // ???
    //How do I verify this was called only once?
    });

    it('should NOT retry a 403 error', () => {
    // ???
    });

    it('should NOT retry a 503 error', () => {
    // ???
    });
    });


    How do I test this so that I can verify that 2 requests were made, and the 2nd one on a delay?

    Continue reading...

Compartilhe esta Página