9

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
  • Execute request using httpMock and inspect actual request/response. angular.io/guide/http#testing-http-requests Commented Sep 8, 2021 at 10:24
  • 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. Commented Sep 8, 2021 at 11:22
  • 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. Commented Sep 9, 2021 at 8:56

4 Answers 4

9

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);
});
Sign up to request clarification or add additional context in comments.

2 Comments

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: () => ...})
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.
3

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.

1 Comment

thanks for the withInterceptorsFromDi !!!! that helps alot, i have been scratching my head for minutes!!!
3

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

5 Comments

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?
I have added an edit. See if it works. I am thinking it should work.
I've tried this but it's not covering the finalize operator section of the code.
Sorry, I am not sure then. I remember I had issues with unit testing interceptor where the headers would be applied after my assertion.
I got one solution and updated the same as answer. Thank you !
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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.