How to properly make mock throw an error in Jest?
Asked Answered
U

4

212

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: '[email protected]' };

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

    console.log(result);

    // test logic
    expect(callMethod).toBeCalledWith({}, 'forgotPassword', {
      email: '[email protected]',
    });

    // 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...

Upkeep answered 14/4, 2018 at 19:18 Comment(2)
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?Shreveport
Do you manage to make this test not log an error to the console? All my mocks which throws causes an error of "Unexpected error" to appear in the console, even with the test passing.Zachariahzacharias
P
363

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

Paludal answered 2/6, 2018 at 11:57 Comment(2)
cool. How to handle it and make and assert?Sec
it throws the error but then the test fails because it has thrown an error. how do we assert?Contortionist
R
47

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()
})
Roberge answered 29/7, 2021 at 18:59 Comment(3)
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.Moribund
@MGDeveloper we dont need try-catch while unit testing and using toThrow() (jestjs.io/docs/expect#tothrowerror). If you try that in your tests, it should work. Can also test in here : codesandbox.io/s/jest-playground-forked-euewe?file=/src/… (sandbox content is transient though). I did edit the terminology from "handling" to "testing" if that was confusingRoberge
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.Sung
D
19

For Angular + Jest:

import { throwError } from 'rxjs';

yourMockInstance.mockImplementation(() => {
  return throwError(new Error('my error message'));
});
Domenicadomenico answered 2/4, 2020 at 16:14 Comment(3)
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.Toadstool
Only returning throw Error should be enough: yourMockInstance.mockImplementation(() => throwError('my error message'));Castellany
Actually using mockReturnValue is enough: mockInstance.mockReturnValue(throwError(() => new Error('my error message')))Lipcombe
B
3

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
    );
});
Beetroot answered 31/3, 2023 at 17:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.