Angular 7 - catching HttpErrorResponse in unit tests

14,563

Solution 1

A late answer, a slightly different way but this works too.

  it('should show modal if failed', inject([Router], (mockRouter: Router) => {
  const errorResponse = new HttpErrorResponse({
     error: { code: `some code`, message: `some message.` },
     status: 400,
     statusText: 'Bad Request',
  });

  spyOn(someService, 'methodFromService').and.returnValue(throwError(errorResponse));
  expect...
  expect...
  expect...
}));

Solution 2

Late answer, but may help someone facing similar issue.

Root cause

The Error Message: "expected data.forEach is not a function to contain '404'" is because of the of operator in the test case:

httpClientSpy.get.and.returnValue(of(errorResponse));

The of operator returns an observable that emits the arguments.

This is useful when you want to return data, but not when you want to raise a 404 error.

In order for the spy to raise error, the response should reject and not resolve.

Solution 1

This solution uses the defer RxJS operator along with jasmine.createSpyObj approach you used in your example.

import { TestBed } from '@angular/core/testing';
import { HttpErrorResponse } from '@angular/common/http';
import { defer } from 'rxjs';

import { CommentsService } from './comments.service';

// Create async observable error that errors
//  after a JS engine turn
export function asyncError<T>(errorObject: any) {
  return defer(() => Promise.reject(errorObject));
}

describe('CommentsService', () => {
  let httpClientSpy: { get: jasmine.Spy };
  let service: CommentsService;

  beforeEach(() => {
    httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);
    service = new CommentsService(httpClientSpy as any);
  });

  it('should throw an error when 404', () => {
    const errorResponse = new HttpErrorResponse({
      error: '404 error',
      status: 404,
      statusText: 'Not Found'
    });

    httpClientSpy.get.and.returnValue(asyncError(errorResponse));

    service.getComments().subscribe(
      data => fail('Should have failed with 404 error'),
      (error: HttpErrorResponse) => {
        expect(error.status).toEqual(404);
        expect(error.error).toContain('404 error');
      });
  });
});

Solution 2

It is better to use the angular HttpClientTestingModule to test the HttpClient usage. The following example shows the same test using HttpClientTestingModule.

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

import { CommentsService } from './comments.service';

describe('CommentsService test using HttpClientTestingModule', () => {
  let httpTestingController: HttpTestingController;
  let service: CommentsService;

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

    httpTestingController = TestBed.get(HttpTestingController);
    service = TestBed.get(CommentsService);
  });

  it('throws 404 error', () => {
    service.getComments().subscribe(
      data => fail('Should have failed with 404 error'),
      (error: HttpErrorResponse) => {
        expect(error.status).toEqual(404);
        expect(error.error).toContain('404 error');
      }
    );

    const req = httpTestingController.expectOne('https://jsonplaceholder.typicode.com/comments');

    // Respond with mock error
    req.flush('404 error', { status: 404, statusText: 'Not Found' });
  });
});

The angular HTTP Testing documentation explains this approach.

Note: The examples are tested using Angular v8.

Share:
14,563
Marius Riehl
Author by

Marius Riehl

Updated on July 19, 2022

Comments

  • Marius Riehl
    Marius Riehl almost 2 years

    i'm currently learning Angular 7 (haven't used any previous version) and encountered something i couldn't fix when writing unit tests for a service.

    I have a service that gets JSON from REST and parses it into a Class. Referring to the Angular Docs, i wrote a test using HttpClientSpy to simulate a 404 Error.

    What happens: The Test Fails with the Error Message: "expected data.forEach is not a function to contain '404'"

    So the Service gets the HttpErrorResponse as Input but tries to parse it like it was a regular response in the map function. This fails, catchError is called and the data.forEach is not a function Error is thrown.

    Expected behavior: i would expect that map() is not executed and it should jump directly into the catchError function.

    How i fixed it (for now): Adding the following lines of code to the map function of the service makes the test work.

    if (data instanceof HttpErrorResponse)
          throw new HttpErrorResponse(data);
    

    The test:

    it('should throw an error when 404', () => {
    
    const errorResponse = new HttpErrorResponse({
      error: '404 error',
      status: 404, statusText: 'Not Found'
    });
    
    httpClientSpy.get.and.returnValue(of(errorResponse));
    
    service.getComments().subscribe(
      fail,
      error => expect(error.message).toContain('404')
    );
    });
    

    The service:

    getComments(): Observable<CommentList> {
    return this.http
    .get('https://jsonplaceholder.typicode.com/comments')
    .pipe(
      map((data: Array<any>) => {
        let t: Array<Comment> = [];
    
        data.forEach(comment => {
    
          if(!('id' in comment) || !('body' in comment) || !('email' in comment) || !('name' in comment))
            throw new Error("Could not cast Object returned from REST into comment");
    
          t.push(<Comment>{
            id: comment.id,
            body: comment.body,
            author: comment.email,
            title: comment.name,
          });
    
        });
        return new CommentList(t);
      }),
      catchError((err: HttpErrorResponse) => {
        return throwError(err);
      })
    );
    }
    

    Am i getting something wrong? I think the expected behavior is what i should experience, at least thats how i interpret the Angular docs.

  • Mike Taverne
    Mike Taverne over 3 years
    Searched for hours, found this answer, awesome! Thanks.