Mocking Observable to throw error in Jest
Asked Answered
E

3

17

I am trying to mock the PUT call of HttpClient of Angular to throw error. I am using throwError for it. It isn't working. What should I change to make it throw the error and call the handleError method? I am using Jest.

it(`should call the 'handleError' method when a request to store data was not successful`, () => {
    const error: HttpErrorResponse = {
      status: 401,
      message: 'You are not logged in',
    } as HttpErrorResponse;

    jest.spyOn(httpClientServiceMock, 'put').mockReturnValue(throwError(error));
    const spy = jest.spyOn(httpService, 'handleError');

    httpService.requestCall('some-url', ApiMethod.PUT, {});
    expect(spy).toBeCalled();
  });

service file

  requestCall(url: string, method: ApiMethod, data?: any): Observable<any> {
    const headers = {
      'X-XSRF-TOKEN': this.xsrfToken,
      'Content-Type': 'application/json; charset=UTF-8',
    };

    const requestConfig = {
      withCredentials: true,
      headers,
    };

    switch (method) {
      case ApiMethod.GET:
        return this._http.get(url, { withCredentials: true });
      case ApiMethod.PUT:
        return this._http
          .put(url, data, requestConfig)
          .pipe(catchError((error) => this.handleError(error)));
    }
  }

  handleError(error: HttpErrorResponse): any {
    if (error.error instanceof ErrorEvent) {
      console.error(`An error occurred: ${error.error.message}`);
    }

    return throwError({ error: error.message, status: error.status });
  }
Enedina answered 8/6, 2020 at 20:20 Comment(0)
B
23

You were pretty close!

You have to subscribe to observable returned from httpService.requestCall('some-url', ApiMethod.PUT, {}) function. There are additional changes required as this is asynchronous

const { of , throwError, operators: {
    catchError
  }
} = rxjs;

const httpClientServiceMock = {
  put: () => of ({
    value: 'test'
  })
};

const httpService = {
  requestCall(url, data, requestConfig) {

    return httpClientServiceMock
      .put(url, data, requestConfig)
      .pipe(catchError((error) => this.handleError(error)));
  },
  handleError(error) {
    return throwError({});
  }
};
const ApiMethod = {
  PUT: ''
}



const {
  expect,
  test,
  run,
  it,
  describe,
  jest
} = jestLite.core;

describe('httpService', () => {

  it(`should call the 'handleError' method when a request to store data was not successful`, done => {
    const error = {
      status: 401,
      message: 'You are not logged in',
    }

    jest.spyOn(httpClientServiceMock, 'put').mockReturnValue(throwError(error));
    const spy = jest.spyOn(httpService, 'handleError');

    httpService
      .requestCall('some-url', ApiMethod.PUT, {})
      .subscribe(pr => {
        done.fail(new Error(`It shouldn't go this path!`))
      }, error => {
        expect(spy).toBeCalled();
        done();
      });

  });

});

run().then(result => {
  console.log(result[0]);
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
Bract answered 8/6, 2020 at 22:50 Comment(1)
This is the reason why done should be avoided where possible because it creates many points of failure. expect(spy).toBeCalled(); done(); - assertion fails, done is never called and results in a timeout, a dev is puzzled. Better use async and toPromise for RxJS testing, at least for completed observables.Parboil
L
2

As already pointed out in the other answer, you have to subscribe to the returned observable.

I just wanted to add another approach which uses marble-testing, so you don't have to manually subscribe to that observable:

let testScheduler;

beforeEach(() => testScheduler = new TestScheduler(assertionFn))

it(`should call the 'handleError' method when a request to store data was not successful`, () => {
  const error = {
    status: 401,
    message: 'You are not logged in',
  } as HttpErrorResponse;

  jest.spyOn(httpClientServiceMock, 'put').mockReturnValue(throwError(error));
  const spy = jest.spyOn(httpService, 'handleError');

  testScheduler.run(({ cold, expectObservable }) => {
    const src$ = httpService.requestCall('some-url', ApiMethod.PUT, {});
    
    expectObservable(src$).toBe('#', null, { error: error.message, status: error.status });
    expect(spy).toBeCalled();
  });
});

TestScheduler is available in rxjs/testing and the run's callback provides several helpers, such as: cold, hot, flush, expectObservable, expectSubscriptions and time.

What I personally like about this is that everything is synchronous, so you might not have to call done() when following such approach.

Longways answered 9/6, 2020 at 7:7 Comment(2)
What is assertionFn in this snippet?Binate
@RobinDeSchepper It is a function that must comply with this signature. Here is an example of how it could look like when testing the filter operator.Suited
A
1

throwError('Message') is deprecated instead use throwError(callback)

example:

     jest.spyOn(serviceClass,'serviceMethodName').mockReturnValueOnce(throwError(()=> {new Error('FAIL')}))
Arciform answered 29/9, 2022 at 10:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.