I recently needed to write some tests for an Angular Interceptor, and could not find easy / good guidance out there, so thought it'd make a good blog post.
What is an Interceptor
An interceptor is a mechanism that is run for every HTTPClient call made by your service. You can use this to add processing to HTTPClient calls. Some uses cases are to add specialized headers, such as authentication headers, to the call. We've also used this to show a generic error upon response from the service.
I'm going to unit test the second case.
The Interceptor Code
You can create a base interceptor with the Angular CLI:
You'll see something like this:
2export class Http403Interceptor implements HttpInterceptor {
3
4 constructor() {}
5
6 intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
7 return next.handle(request);
8 }
9}
To handle a generic error we'll want to pipe off the return request and check for a 403 status inside a catchError operator:
2 catchError((err: HttpErrorResponse) => {
3 if (err.status === 403) {
4 // do something
5 this.errorOccurred = true;
6 }
7 return throwError(() => err);
8 })
9 );
The interceptor had to do something for us to test, so I toggled an instance variable on the Interceptor class, be sure to define that variable:
In a real world scenario, we'd probably do something more complex such as show a warning to the user.
The Tests
The Angular CLI gave us a generic test, but we'll need to expand that. First, add some variables:
2 let httpMock: HttpTestingController;
3 let interceptor: Http403Interceptor;
We have an instance of the Interceptor. And also an instance of an HttpClient to and HttpTestingController which will be used to make mock HTTP Calls which will be intercepted. Let's add in a URL to load and an error event:
2 const error: ProgressEvent = new ProgressEvent('error');
Next we need to define the TestBed, which I do in a beforeEach() block:
2 imports: [HttpClientTestingModule],
3 providers: [
4 {
5 provide: HTTP_INTERCEPTORS,
6 useClass: Http403Interceptor,
7 multi: true
8 }
9 ]
10 }).compileComponents()
11 );
The TestBed loads the HttpClientTestingModule, and sets up the 403 interceptor as a provider.
Let's use another beforeEach() block to populate our variables:
2 httpClient = TestBed.inject(HttpClient);
3 httpMock = TestBed.inject(HttpTestingController);
4 interceptor = TestBed.inject(HTTP_INTERCEPTORS)[0] as Http403Interceptor
5 });
The httpClient and httpMock are both retrieved using the inject command on the TestBed. Both of these are imported from the use of the HttpClientTestingModule. The interceptor is retrieved a bit differently; retrieving the HTTP_INTERCEPTORS, which returns an array of all interceptors. We know our interceptor is the first--and only--one defined, so we get it by parsing the array returned.
Now, create test:
When we name a test, I like to give details about the action and the condition.
Inside the test, let's start with an assertion to make sure our interceptor.errorOccured value is falsy:
Then, use the httpClient to start a request:
2 next: () => {},
3 error: (err: HttpErrorResponse) => {
4 expect(err.error).toEqual(error);
5 expect(interceptor.errorOccurred).toBeTruthy();
6 }
7 });
For this test, we only care about the error condition; not the success condition. We have two assertions, we verify that the errorOccurred value was toggled and that the error object is equal to our error object.
Finally, we want to create the HTTP Request, and trigger an error response:
2 req.error(error, {status: 403});
Run the test and you'll see all success. Play with a sample project here