How to unit test an HTTP interceptor in Angular using Jasmine
Asked Answered
D

4

7

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.

Distant answered 8/9, 2021 at 9:51 Comment(3)
Execute request using httpMock and inspect actual request/response. angular.io/guide/http#testing-http-requestsChore
It's not showing any covered code because you are not running any of that code in your spec. All the code you provided holds no obvious errors and neither does your question, so please, update your question with an actual error or code that you've tried to far, explaining what does not meet your expectations.Barkeeper
I've updated the spec code. I can cover few lines of code with the above updated code but still I'm not able to cover from finalize method. Can anyone help here.Distant
D
8

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);
});
Distant answered 13/9, 2021 at 12:34 Comment(2)
I know this question is already a bit older, but it should be mentioned, that in this answer (at least in Angular 12) the test will pass false positive. The handler completes without emitting a value that the subscribe method catches. To catch the complete event one need to use .subscribe({complete: () => ...})Burrton
A part I'm struggling with is getting the response. return next.handle(example). Complete doesn't have any callback and a simple subscribe doesn't return anything. Any thoughts? My use case is testing to see if headers have been appended with a bearer token. And I've made things more spicy by not using testbed lol.Bute
E
2

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

Elegist answered 8/9, 2021 at 12:58 Comment(5)
The shared link was useful. I have updated the code with that and I'm able to cover few lines of code but still I'm not able to cover the lines from finalize method. Can you help to cover those lines of code?Distant
I have added an edit. See if it works. I am thinking it should work.Elegist
I've tried this but it's not covering the finalize operator section of the code.Distant
Sorry, I am not sure then. I remember I had issues with unit testing interceptor where the headers would be applied after my assertion.Elegist
I got one solution and updated the same as answer. Thank you !Distant
P
1

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

Philomena answered 8/9, 2021 at 13:1 Comment(0)
I
1

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.

Inquire answered 22/7 at 19:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.