knbibin
knbibin

Reputation: 1211

How to unit test an HTTP interceptor in Angular using Jasmine

I have the below http interceptor in my angular application and I would like to unit test the same using Jasmine. I googled some of them and tried but its not working as expected. Please find the below HttpInterceptorService.ts file code

export class HttpInterceptorService Implements HttpInterceptor {
 counter = 0;
 constructor(private loaderService: LoaderService) { }
 intercept(req: HttpRequest<any>, next: HttpHandler) {
  if (req.url !== '/getUsers') {
   this.counter ++;
  }
  this.loaderService.setStatus(true);
  return next.handle(req).pipe(
   finalize(() => {
    if (req.url !== 'getUsers') {
      this.counter --;
    }
    if (this.counter === 0) {
      this.loaderService.setStatus(false);
    }
   };
  );
 }
}

Below are the HttpInterceptor.service.spec.ts file code which I tried as of now. Im not sure how to test the particular method in it.

describe('HttpInterceptorService', () => {
  let httpService: HttpService;
  let httpMock: HttpTestingController;
  let interceptor: HttpInterceptorService;

  beforeEach(()=> {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
       HttpService,
       {provide:HTTP_INTERCEPTOR, useClass: HttpInterceptorService, multi: true},
      ]
    });
    httpService = TestBed.get(HttpService);
    httpMock = TestBed.get(HttpTestingController);
    interceptor = TestBed.get(HttpInterceptorService);
  });

   it('should increment the counter for all api's expect getUsers', ()=> {
      httpService.get('getAdminList').subscribe(res => {
        expect(res).toBeTruthy();
        expect(interceptor.counter).toBeGreaterThan(0);
      });
   });

   
});

after checking the reference code I'm able to cover few lines of code with above changes. But I'm still not able to cover the finalize method. Request to kindly help.

Upvotes: 8

Views: 27670

Answers (4)

AliF50
AliF50

Reputation: 18809

Remove HttpInterceptorService from the providers because you are already providing it on the next line with { provide:HTTP_INTERCEPTOR, .... Try to follow this guide: https://alligator.io/angular/testing-http-interceptors/. It seems like you need to have a service that actually makes API calls. Try to follow this guide as well: https://www.mobiquity.com/insights/testing-angular-http-communication

I think to make an HTTP call, you can just do httpClient.get('www.google.com').subscribe() and you shouldn't need an actual service (DataService) like the first guide shows.

Edit:

describe('HttpInterceptorService', () => {
  let httpService: HttpService;
  let httpMock: HttpTestingController;
  let interceptor: HttpInterceptorService;
  // mock your loaderService to ensure no issues
  let mockLoaderService = { setStatus: () => void };

  beforeEach(()=> {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
       HttpService,
       {provide:HTTP_INTERCEPTOR, useClass: HttpInterceptorService, multi: true},
       // provide the mock when the unit test requires
       // LoaderService
       { provide: LoaderService, useValue: mockLoaderService },
      ]
    });
    httpService = TestBed.get(HttpService);
    httpMock = TestBed.get(HttpTestingController);
    interceptor = TestBed.get(HttpInterceptorService);
  });

   it("should increment the counter for all api's except getUsers", ()=> {
      httpService.get('getAdminList').subscribe(res => {
        expect(res).toBeTruthy();
        expect(interceptor.counter).toBeGreaterThan(0);
      });
   });
   // add this unit test
      it('should decrement the counter for getUsers', ()=> {
      httpService.get('getUsers').subscribe(res => {
        expect(res).toBeTruthy();
        expect(interceptor.counter).toBe(0);
      });
   });
});

Upvotes: 3

doc.aicdev
doc.aicdev

Reputation: 116

Don’t know if the topic is still hot but: https://medium.com/@js_9757/angular-unit-test-the-http-interceptor-c2464cf8e8da

Example with Angular 18 and Jest

Given that Interceptor code:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AccessTokenInterceptor implements HttpInterceptor {

  private readonly TOKEN_KEY: string = 'access_token';

  public intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {


    return next.handle(httpRequest.clone(
      {
        setHeaders: this.getAuthInterceptorToken(),
      },
    ))
  }

  private getAuthInterceptorToken(): any {
    return {
      'Authorization': `Bearer ${localStorage.getItem(this.TOKEN_KEY)}`,
    };
  }
}

Can test with this test code:

import { TestBed } from '@angular/core/testing';
import { HTTP_INTERCEPTORS, HttpClient, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { AccessTokenInterceptor } from './http.interceptor';

describe('HttpInterceptor', () => {

  let httpMock: HttpTestingController;
  let httpClient: HttpClient;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(withInterceptorsFromDi()),
        provideHttpClientTesting(),
        { provide: HTTP_INTERCEPTORS, useClass: AccessTokenInterceptor, multi: true },
      ],
    });

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

  afterEach(() => {
    httpMock.verify();
  });


  it('should add Authorization header', () => {

    jest.spyOn(Storage.prototype, 'getItem');
    Storage.prototype.getItem = jest.fn().mockReturnValue('test_token')


    httpClient.get('/test').subscribe();
    const req = httpMock.expectOne('/test');

    expect(req.request.headers.has('Authorization')).toBeTruthy();
    expect(req.request.headers.get('Authorization')).toBe('Bearer test_token');

  });
})

May this is helpful.

Upvotes: 2

knbibin
knbibin

Reputation: 1211

the below code helps to cover the code inside finalize operator.

const next: any = {
  handle: () => {
    return Observable.create(subscriber => {
      subscriber.complete();
    });
  }
};

const requestMock = new HttpRequest('GET', '/test');

interceptor.intercept(requestMock, next).subscribe(() => {
  expect(interceptor.counter).toBeGreaterThan(0);
});

Upvotes: 9

Aaron Gibson
Aaron Gibson

Reputation: 1378

Living Example@

describe('AuthHttpInterceptor', () => {
let http: HttpClient,
    httpTestingController: HttpTestingController,
    mockAuthService: AuthorizationService;

beforeEach(() => {
    TestBed.configureTestingModule({
        imports: [HttpClientTestingModule, SharedModule],
        providers: [
            {
                provide: AuthorizationService,
                useClass: MockAuthorizationService
            },
            {
                provide: HTTP_INTERCEPTORS,
                useClass: AuthorizationInterceptor,
                multi: true
            },
            // One of these tests trigger a console.error call and is expected
            // Mocking the logger prevents this otherwise another test run outside this suite
            // to prevent console.error calls will fail.
            {
                provide: LoggerInjectionToken,
                useValue: mockLogger
            }]
    });

    http = TestBed.inject(HttpClient);
    httpTestingController = TestBed.inject(HttpTestingController);
    mockAuthService = TestBed.inject(AuthorizationService);
});

Example Test:

it('will refresh token and re-issue request should 401 be returned.', (() => {
    spyOn(mockAuthService, 'requestNewToken').and.callFake(() => {
        return of({
            renewed: true,
            accessToken: 'token'
        });
    });

    http.get('/data')
        .subscribe((data) => {
            expect(data).toEqual('Payload');
        });

    const failedRequest = httpTestingController.match('/data')[0];
    failedRequest.error(new ErrorEvent('Er'), { status: 401 });

    const successReq = httpTestingController.match('/data')[0];
    successReq.flush('Payload', { status: 200, statusText: 'OK' });

    expect(mockAuthService.requestNewToken).toHaveBeenCalled();

    httpTestingController.verify();
}));

Depends on what's needed directly

Upvotes: 1

Related Questions