How to unit test an HTTP interceptor in Angular using Jasmine

Viewed 7935

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.

3 Answers

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);
});

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);
      });
   });
});

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

Related