Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking Observable to throw error in Jest

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 });
  }
like image 796
Pritam Bohra Avatar asked Jun 08 '20 20:06

Pritam Bohra


People also ask

How do you throw an error in Jest mock?

To properly make mock throw an error in Jest, we call the mockImplementation method and throw an error in the callback we call the method with. it("should throw error if email not found", async () => { callMethod . mockImplementation(() => { throw new Error("User not found [403]"); }) .

How do you throw error in Jest?

to use throw to thrown an error in the mocked implementation of yourMockInstance . If we're mocking async functions, we can use mockRejectedValue to mock the value of a rejected promise returned by the async function. test('async test', async () => { const yourMockFn = jest. fn().

How do you mock a value in Jest?

To mock the return value of an imported function in Jest, you have to either call mockReturnValue or mockImplementation on a Jest mock function and then specify the return value. Which function mock function you should use depends on your situation.


Video Answer


2 Answers

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>
like image 119
Józef Podlecki Avatar answered Nov 12 '22 03:11

Józef Podlecki


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.

like image 40
Andrei Gătej Avatar answered Nov 12 '22 04:11

Andrei Gătej