202

I'm testing my GraphQL api using Jest.

I'm using a separate test suit for each query/mutation

I have 2 tests (each one in a separate test suit) where I mock one function (namely, Meteor's callMethod) that is used in mutations.

  it('should throw error if email not found', async () => {
    callMethod
      .mockReturnValue(new Error('User not found [403]'))
      .mockName('callMethod');

    const query = FORGOT_PASSWORD_MUTATION;
    const params = { email: 'user@example.com' };

    const result = await simulateQuery({ query, params });

    console.log(result);

    // test logic
    expect(callMethod).toBeCalledWith({}, 'forgotPassword', {
      email: 'user@example.com',
    });

    // test resolvers
  });

When I console.log(result) I get

{ data: { forgotPassword: true } }

This behaviour is not what I want because in .mockReturnValue I throw an Error and therefore expect result to have an error object

Before this test, however, another is ran

 it('should throw an error if wrong credentials were provided', async () => {
    callMethod
      .mockReturnValue(new Error('cannot login'))
      .mockName('callMethod');

And it works fine, the error is thrown

I guess the problem is that mock doesn't get reset after the test finishes. In my jest.conf.js I have clearMocks: true

Each test suit is in a separate file, and I mock functions before tests like this:

import simulateQuery from '../../../helpers/simulate-query';

import callMethod from '../../../../imports/api/users/functions/auth/helpers/call-accounts-method';

import LOGIN_WITH_PASSWORD_MUTATION from './mutations/login-with-password';

jest.mock(
  '../../../../imports/api/users/functions/auth/helpers/call-accounts-method'
);

describe('loginWithPassword mutation', function() {
...

UPDATE

When I substituted .mockReturnValue with .mockImplementation everything worked out as expected:

callMethod.mockImplementation(() => {
  throw new Error('User not found');
});

But that doesn't explain why in another test .mockReturnValue works fine...

dwjohnston
  • 11,163
  • 32
  • 99
  • 194
Le garcon
  • 7,197
  • 9
  • 31
  • 46
  • 1
    It looks like your mock is *returning* an error object, not _throwing_ it. Without seeing your code that you are testing, I can only share the experience I had. I forgot to mock a function called in my mutation, which caused an error to be thrown unintentionally. Perhaps there is something similar happening for you? – Rhuarc13 May 14 '18 at 13:54

4 Answers4

345

Change .mockReturnValue with .mockImplementation:

    yourMockInstance.mockImplementation(() => {
      throw new Error();
    });

in case you want to assert

   test('the fetch fails with an error', () => {
     return expect(fetchData()).rejects.toMatch('error');
   });

If it's a promise you can also to .rejects www.jestjs.io/docs/en/asynchronous#resolves--rejects

eduardomoroni
  • 4,140
  • 1
  • 21
  • 18
38

For promises, can use https://jestjs.io/docs/mock-function-api#mockfnmockrejectedvaluevalue

test('async test', async () => {
  const asyncMock = jest.fn().mockRejectedValue(new Error('Async error'));

  await asyncMock(); // throws "Async error"
});

For testing that error was thrown or not, can use https://eloquentcode.com/expect-a-function-to-throw-an-exception-in-jest

const func = () => {
  throw new Error('my error')
}
it('should throw an error', () => {
    expect(func).toThrow()
})
gawkface
  • 2,104
  • 2
  • 27
  • 29
  • 2
    You would also need a try and catch in your expect otherwise it would not assert correctly. Can you please improve your answer or reply if I am missing something. – MG Developer Nov 24 '21 at 16:57
  • 1
    @MGDeveloper we dont need try-catch while unit testing and using toThrow() (https://jestjs.io/docs/expect#tothrowerror). If you try that in your tests, it should work. Can also test in here : https://codesandbox.io/s/jest-playground-forked-euewe?file=/src/sum.test.js:210-217 (sandbox content is transient though). I did edit the terminology from "handling" to "testing" if that was confusing – gawkface Nov 27 '21 at 03:42
  • 1
    I found the mockRejectedValue helpful in the case that the asynchronous unit I was testing handled the exception thrown in a specific way that I wanted to test, therefore in that case a catch or toThrow() would not be needed. – CortexCompiler Aug 10 '22 at 19:31
17

For Angular + Jest:

import { throwError } from 'rxjs';

yourMockInstance.mockImplementation(() => {
  return throwError(new Error('my error message'));
});
Sean W
  • 527
  • 6
  • 15
AngularBoy
  • 1,715
  • 2
  • 25
  • 35
  • 2
    Technically this isn't a `throw` in the pure JS sense. You are configuring the mock to return a RXJS observable object which immediately emits an error notification. Still, maybe handy for folks to see here. The accepted answer certainly *will* make a mock throw an error. In all cases. – Zach Lysobey Sep 16 '20 at 21:57
  • Only returning `throw Error` should be enough: `yourMockInstance.mockImplementation(() => throwError('my error message'));` – manzapanza Apr 30 '21 at 21:42
  • Actually using `mockReturnValue` is enough: `mockInstance.mockReturnValue(throwError(() => new Error('my error message')))` – RcoderNY Jun 19 '22 at 05:41
0

make sure to add throw new Error('Network error or something') either in catch block or conditionally.

import fetchApi from '../src'

it("should throw error", async () => {
    const errorMessage: string = "Network Error";
    
    (axios.post as jest.Mock).mockRejectedValueOnce(new Error(errorMessage));
    
    expect(async () => await fetchApi()).rejects.toThrow(
          errorMessage
    );
});
Ericgit
  • 6,089
  • 2
  • 42
  • 53