Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test this Angular typescript Http Error Interceptor that catches errors from a piped observable?

I am running an experiment where I am learning angular and typescript via testing someone else code (e.g. automated unit and end to end tests). After I get it under test, I plan to repurposes it for a pet project I am working on for a university classroom.

I am at least half way through unit testing the code from here: http://jasonwatmore.com/post/2018/05/16/angular-6-user-registration-and-login-example-tutorial

I have been trying for some time to get the following code under unit test but everything I have tried from my own ideas or ideas from the internet have been unsuccessful thus far:

import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http";
import { AuthenticationService } from "src/app/authenticationService/AuthenticationService";
import { Observable, throwError } from "rxjs";
import { catchError } from "rxjs/operators";
import { Injectable } from "@angular/core";

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
    constructor(private authenticationService: AuthenticationService) {}

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        console.log('before error handle')
        return next.handle(request).pipe(catchError(err => {

            console.log('in error handle')

            if (err.status === 401) { 
                // auto logout if 401 response returned from api
                this.authenticationService.logout();
                location.reload(true);
            }

            const error = err.error.message || err.statusText;
            return throwError(error);
        }))
    }

}

The following test code and several variations have been unsuccessful to get the 'in error handle' message to show up in the console log:

import { ErrorInterceptor } from "./ErrorInterceptor";
import { of, throwError, defer } from "rxjs";

describe('ErrorInterceptor', () => {
    let errorInterceptor;
    let authenticationServiceSpy;

    beforeEach(() => {
        authenticationServiceSpy = jasmine.createSpyObj('AuthenticationService', ['logout']);
        errorInterceptor = new ErrorInterceptor(authenticationServiceSpy);
    })

    it('should create', () => {
        expect(errorInterceptor).toBeTruthy();
    })

    describe('intercept', () => {
        let httpRequestSpy;
        let httpHandlerSpy;
        const error = {status: 401, statusText: 'error'};

        it('should auto logout if 401 response returned from api', () => {
            //arrange
            httpRequestSpy = jasmine.createSpyObj('HttpRequest', ['doesNotMatter']);
            httpHandlerSpy = jasmine.createSpyObj('HttpHandler', ['handle']);
            httpHandlerSpy.handle.and.returnValue({
                pipe: () => {
                return fakeAsyncResponseWithError({});
                }
            });

            //act
            errorInterceptor.intercept(httpRequestSpy, httpHandlerSpy);

            //assert
            //TBD

            function fakeAsyncResponseWithError<T>(data: T) {
                return defer(() => throwError(error));
            }
        })
    })
})
like image 390
Douglas Chadwick Jr. Avatar asked Dec 08 '18 15:12

Douglas Chadwick Jr.


People also ask

How does Angular handle HTTP errors?

The basic way to handle errors in Angular is to use Angular's HttpClient service along with RxJS operators throwError and catchError. The HTTP request is made, and it returns the data with a response if anything wrong happens then it returns an error object with an error status code.

What is test TS file in Angular?

First, ng test uses Webpack to compile your code into a JavaScript bundle. The entry point for the bundle is src/test. ts . This file initializes the Angular testing environment – the TestBed – and then imports all files in the directory tree that match the pattern . spec.

What is the purpose of the fixture detectChanges () call in this unit test?

Fixtures have access to a debugElement , which will give you access to the internals of the component fixture. Change detection isn't done automatically, so you'll call detectChanges on a fixture to tell Angular to run change detection.


1 Answers

A couple of issues here.

  • First, your return value from httpHandlerSpy.handle() needs to be an Observable, since that will already have the pipe operator on it and then the HttpInterceptor code can pipe it to catchError as required.
  • Second, HttpInterceptor returns an Observable and for this to be "executed", it needs to be subscribed to.

I put together a Stackblitz to demonstrate how I'd approach this.

From the Stackblitz, here is the spec (it function):

it('should auto logout if 401 response returned from api', () => {
    //arrange
    httpRequestSpy = jasmine.createSpyObj('HttpRequest', ['doesNotMatter']);
    httpHandlerSpy = jasmine.createSpyObj('HttpHandler', ['handle']);
    httpHandlerSpy.handle.and.returnValue(throwError(
        {error: 
            {message: 'test-error'}
        }
    ));
    //act
    errorInterceptor.intercept(httpRequestSpy, httpHandlerSpy)
        .subscribe(
            result => console.log('good', result), 
            err => { 
                console.log('error', err);
                expect(err).toEqual('test-error');
            }
        );

    //assert

})

I hope this helps.

like image 175
dmcgrandle Avatar answered Sep 21 '22 22:09

dmcgrandle