How to successfully mock and catch an error using Jest?

12,106

Solution 1

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.

Solution 2

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.

Share:
12,106

Related videos on Youtube

tjol
Author by

tjol

Updated on June 04, 2022

Comments

  • tjol
    tjol almost 2 years

    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.
    
    • thopaw
      thopaw almost 5 years
      I would suggest that you give spyOn a try like in const spy = jest.spyOn(userArray, 'filter').mockImplementation(() => throw new Error()). jestjs.io/docs/en/jest-object#jestspyonobject-methodname
    • Teneff
      Teneff almost 5 years
      I've setup the test repl.it/repls/MedicalIntelligentTriangles and it seems to be working fine
    • tjol
      tjol almost 5 years
      @Teneff hmmm that is strange. The repl.it example you posted above seems to work for me as well. However, I still get an error when I run this test locally :( I have edited the post to include the error I am getting.
  • tjol
    tjol almost 5 years
    Thank you, that did the trick! Great explanation. Only thing I tweaked was the mocking of the function. userArray.filter = jest.fn().mockImplementation(() => {throw new Error()});