Angular unit test failed HttpClient requests

Viewed 2365

I'm trying to unit test a service I have that is responsable to do http calls. I could test for a success request, but I'm not able to make the tests when the status code of the response is different from 200.

For example, let's say the request return a 404 status, then I'm not able to test it properly.

This is my service:

@Injectable()
export class ApiService {
    constructor(
        private _http: HttpClient,
        private _router: Router,
        private _toast: ToastsManager,
        private _auth: AuthService,
    ) { }

    public apiGet(url: string) {
        return this._http
            .get(url)
            .catch(this.handleError.bind(this));
    }

    private handleError(error) {
        if (error.status === 401) {
            this._auth.logout();
            return Observable.throw(error);
        }

        if (error.status === 404) {
            this._router.navigateByUrl('not-found');
            return Observable.throw(error);
        }

        if (error.error && error.error.message) {
            this._toast.error(error.error.message);
        } else {
            this._toast.error('Something went wrong');
        }

        return Observable.throw(error);
    }
}

And this is how I'm testing:

describe('ApiService', () => {
    let service: ApiService;
    let backend: HttpTestingController;

    const mockSuccessResponse = { value: '123', name: 'John' };
    const mockSuccessStatus = { status: 200, statusText: 'Ok' };

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [
                MockModule,
                HttpClientTestingModule,
            ],
            providers: [
                ApiService,
            ]
        });

        service = TestBed.get(ApiService);
        backend = TestBed.get(HttpTestingController);
    });

    it('should call the apiGet() function with success', () => {
        service.apiGet('mock/get/url').subscribe(response => {
            expect(response).toEqual(mockSuccessResponse);
        });

        const req = backend.expectOne('mock/get/url');

        expect(req.request.url).toBe('mock/get/url');
        expect(req.request.method).toBe('GET');

        req.flush(mockSuccessResponse, mockSuccessStatus);
    });

    it('should execute handleError function on status different of 200', () => {
        service.apiGet('mock/error/url').subscribe(response => { }, error => {
            // Handle the error cases here (?)
        });

        const req = backend.expectOne('mock/error/url');

        req.flush(null, { status: 404, statusText: 'Not Found' });
    });

    afterEach(() => {
        backend.verify();
    });
});

I don't know how to continue from here. If I try to do something like expect(service.handleError()).toHaveBeenCalled(); I'll get errors like handleError is a private method.

I also need to test if the function logout() on the authService will be called or if the route change to not-found on 404 errors.

What should I do to be able to test those cases, where the status of the response is different from 200?

1 Answers

The way I solve this issue was to test if the handleError function is called and then create another set of tests to test the handleError function alone.

it('should execute handleError function on status different of 200', () => {
    // "as any" to avoid the "private method" type errors
    spyOn(service as any, 'handleError');

    service.apiGet('mock/get/url').subscribe(() => { }, error => {
        // This is where I just check if the function was called
        // It also needs to be service['handleError'] to avoid type errors on private method
        expect(service['handleError']).toHaveBeenCalled();
    });

    const req = backend.expectOne('mock/get/url');

    req.flush(null, { status: 400, statusText: 'Error' });
});

Then I create another set of tests where I manually set the error code I'm trying to test, 404 for example:

it('should navigate to route "not-found" on status 404', () => {
    spyOn(router, 'navigateByUrl');

    const mockError = {
        status: 404,
        error: {
            message: 'Page not found',
        },
    };

    service['handleError'](mockError);

    // Here we adapt to test whatever condition it is
    // Mine is a simple redirect to an error page
    expect(router.navigateByUrl).toHaveBeenCalledWith('not-found');
});
Related