tjol
tjol

Reputation: 33

How to successfully mock and catch an error using Jest?

I've been stuck trying to create a specific test for a few days now, and would appreciate any insight into what I may be doing wrong.

I am trying to mock out the Array filter function to throw an error.

userHelper.js

//filter users by email ending
const filterUsersByEmailDomain = (usersArray, emailEnding) => {
    try {
        let bizUsers = usersArray.filter(user => {
            return user.email.endsWith(emailEnding);
        });
        return bizUsers;
    } catch (err) {
        console.log('error filtering users. Throwing error.');
        throw err;
    }
}

userHelper.test.js:

it('should throw', () => {
        const user1 = {id: 1, email: '[email protected]'};
        const user2 = {id: 2, email: '[email protected]'};
        const userArray = [user1, user2];
        const domainEnding = '.biz';

        Array.prototype.filter = jest.fn().mockImplementation(() => {throw new Error()});

        expect(() => {usersHelper.filterUsersByEmailDomain(userArray, domainEnding)}).toThrow();
    });

From what I can tell, the error is being thrown, but isn't successfully being caught. I've also tried making the called to usersHelper.filterUsersByEmailDomain() within a try catch block as i have seen others do, but was also unsuccessful. Thanks in advance!

Edit: Here is the error I receive when running this test setup locally in my project.

  ● Testing the usersHelper module › should throw



      56 |         const domainEnding = '.biz';
      57 | 
    > 58 |         Array.prototype.filter = jest.fn().mockImplementation(() => {throw new Error()});
         |                                                                            ^
      59 | 
      60 |         expect(() => {usersHelper.filterUsersByEmailDomain(userArray, domainEnding)}).toThrow();
      61 |     });

      at Array.filter.jest.fn.mockImplementation (utils/__tests__/usersHelper.test.js:58:76)
      at _objectSpread (node_modules/expect/build/index.js:60:46)
      at Object.throwingMatcher [as toThrow] (node_modules/expect/build/index.js:264:19)
      at Object.toThrow (utils/__tests__/usersHelper.test.js:60:87)

(node:32672) UnhandledPromiseRejectionWarning: Error
(node:32672) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .c
atch(). (rejection id: 2)
(node:32672) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Upvotes: 3

Views: 25387

Answers (2)

Brian Adams
Brian Adams

Reputation: 45850

Array.prototype.filter is a very low-level function and mocking it to throw an error can cause your tests to not run properly.

Take this simple test:

it('should throw', () => {
  expect(() => { throw new Error() }).toThrow();  // Success!
});

...which works fine...

...but mock Array.prototype.filter to throw an error and it fails:

it('should throw', () => {
  Array.prototype.filter = jest.fn(() => { throw new Error() });
  expect(() => { throw new Error() }).toThrow();  // Fail!
});

Instead, just mock filter on the array itself:

it('should throw', () => {
  const user1 = { id: 1, email: '[email protected]' };
  const user2 = { id: 2, email: '[email protected]' };
  const userArray = [user1, user2];
  const domainEnding = '.biz';

  userArray.filter = () => { throw new Error() };  // <= mock filter on userArray

  expect(() => { usersHelper.filterUsersByEmailDomain(userArray, domainEnding) }).toThrow();  // Success!
});

JavaScript looks for a property on the object itself before checking its prototype so the mock filter on userArray gets called in filterUsersByEmailDomain and the test passes as expected.

Upvotes: 5

0x777C
0x777C

Reputation: 1047

You want to put your toThrow() before the execution of the tested function, in Jest 'toX' means that it must be setup beforehand e.g: toBeCalled(). This is why toHaveBeenCalled() exists, as this form allows the assertion to happen after the code has run.

Upvotes: 1

Related Questions