27

Can you tell me how to test the HttpInterceptor provided by the Angular 4. I have created an interceptor as per the examples but not sure how to test it. Below is my interceptor and I want to test if the custom headers are added and when response status is 401 window.location.href is done.

export class MyInterceptor implements HttpInterceptor {

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const headers = new HttpHeaders();
        this.addHeader(headers); // This will add headers

        const changedReq = req.clone({ headers: headers });
        return next.handle(req)
            .catch(err => {
                if (err instanceof HttpErrorResponse) {
                    switch (err.status) {
                        case 302:
                        case 401:
                            window.location.href = "http//google.com";
                            break;             
                        default:
                            throw new Error(this.getErrorMessage(err));
                    }
                }

                return Observable.throw(err);
            });
    }
danwellman
  • 9,068
  • 8
  • 60
  • 88
Angad
  • 1,144
  • 3
  • 18
  • 39

5 Answers5

38

I got stuck testing a similar thing, but thanks to Alisa's article Intercepting HTTP Requests I got it to work

    import {TestBed, inject} from '@angular/core/testing';
    import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
    import {HTTP_INTERCEPTORS, HttpClient} from '@angular/common/http';

    import {LangInterceptorService} from './lang-interceptor.service';

    describe('Lang-interceptor.service', () => {
       beforeEach(() => TestBed.configureTestingModule({
             imports: [HttpClientTestingModule],
             providers: [{
                         provide: HTTP_INTERCEPTORS,
                         useClass: LangInterceptorService,
                         multi: true
              }]
       }));

       describe('intercept HTTP requests', () => {
            it('should add Accept-Language to Headers', inject([HttpClient, HttpTestingController],
              (http: HttpClient, mock: HttpTestingController) => {

                   http.get('/api').subscribe(response => expect(response).toBeTruthy());
                   const request = mock.expectOne(req => (req.headers.has('Accept-Language') && req.headers.get('Accept-Language') === 'ar'));

                   request.flush({data: 'test'});
                   mock.verify();
             }));
        });

        afterEach(inject([HttpTestingController], (mock: HttpTestingController) => {
             mock.verify();
        }));
    });
ramon22
  • 3,528
  • 2
  • 35
  • 48
11

I'm a bit late to the post, but I figured out a way of testing the interceptors outside of Angular's context. This means that you don't have to mock HTTP calls, you only test the intercept function like any Javascript function.

Let's say your interceptor only does that, which is displaying a log if the error status is 500 :

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  return next
    .handle(req)
    .catch((err: HttpErrorResponse) => {
      if(err.status === 500) { console.log('Server error'); }
    });
}

Then, in your service, you can mock the parameters of your function like so :

const err: any = { status: 500 };
const next: any = {
  handle: (request: HttpRequest<any>) => ({
    catch: (callback: Function) => callback(err)
  })
};

With that, you can write a test for your interceptor :

it('should write a console log with error status equal to 500', () => {
  spyOn(console, 'log');

  service.intercept({} as any, next);

  expect(console.log).toHaveBeenCalled();
});

And voilà !

  • Hi, i don't understand where does it come the variable service. did you injected it? do you have a full example? – Pedro Tainha Apr 27 '18 at 10:54
  • 1
    Hi ! No I didn't inject it, I used `TestBed.get(YourService)`. But you can inject it if you want, that's not an issue ! I don't know the best practice, I just know that I prefer using `TestBed.get(YourService)` rather than the `inject()` function. –  Apr 27 '18 at 10:56
  • 1
    I don't believe this would be useful as the point of the interceptor is to be used in angular's context. I considered doing this at first to help separate concerns but realistically I want to ensure that my interceptor works in angular as expected. Consider angular changes how interceptors work in vNext. This test would still pass, but the application wouldn't work as expected. – vipero07 Jul 09 '18 at 15:00
  • 6
    Depends on what your conception of a unit test is. For me, the intercept method of the interceptor is supposed to do something (in this case, log). So you can unit test it, outside of Angular's context. And if you want to test that, for instance, the headers are appended to your request, you can start by testing that headers are indeed appended in this isolated test, plus test that your requests, made by your services, go with the correct headers. –  Jul 09 '18 at 15:26
  • How do I do this with the latest version of rxjs? Getting this error `TypeError: next.handle(...).pipe is not a function` – Michael McKenna Jun 29 '20 at 17:30
5

I wanted to get the response from the request modified by the interceptor, so I used the callback method of the handle object.

Test:

it("should set header authorization", async(() => {
    const token: string = "token_value";        

    let response: HttpResponse<any>;

    const next: any = {
      handle: responseHandle => {
        response = responseHandle;
      }
    };

    const request: HttpRequest<any> = new HttpRequest<any>("GET", `${API_URL}`);

    tokenInterceptor.intercept(request, next);

    expect(response.headers.get("Authorization")).toEqual(token);
}));

I also used a service mock that generates the token to control the value I wanted to validate.

Moisés Filho
  • 102
  • 1
  • 5
3

Just make any call and mock the response with .error() method of HttpTestingController and it should work.

describe('Error interceptor', function () {
let http: HttpTestingController;
  let httpClient: HttpClient;

  beforeEach(() => {
    const testBed = TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        {
          provide: HTTP_INTERCEPTORS,
          useClass: MyInterceptor,
          multi: true
        }
      ],
    });

 http = testBed.get(HttpTestingController);
 httpClient = testBed.get(HttpClient);
 });

  it('should catch 401', function (done) {
    httpClient.get('/error').subscribe(() => {}, () => {
      // Perform test
      done();
    });

    http.expectOne('/error').error(new ErrorEvent('Unauthorized error'), {
      status: 401
    });
    http.verify();
  });

});
Julian
  • 609
  • 3
  • 7
2

Interceptor testing is similar to Testing of Angular service. And TestBed is going to provide all you need to test them.

beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            providers: [
                {
                    provide: HTTP_INTERCEPTORS,
                    useClass: MyInterceptor,
                    multi: true
                }]
        });
    });


describe('making http calls', () => {
        it('adding header test', inject([HttpClient, YourMock], (http: HttpClient, httpMock: YourMock) => {

            http.get('/data').subscribe(
                response => {
                    expect(response).toBeTruthy();
                }
            );

            expect(response.status).toEqual('401');
        }));
    });

Mocking your service will give you data which you want to duplicate during test.

Aniruddha Das
  • 20,520
  • 23
  • 96
  • 132
  • Thanks but I don't understand what is yourMock in this case. I want to make http.get call and want to check if the interceptor added the headers and want to mock the response. – Angad Sep 14 '17 at 20:15
  • you can skip that. its not required if you are not mocking anything. – Aniruddha Das Sep 15 '17 at 03:29