424

I'm writing an async test that expects the async function to throw like this:

it("expects to have failed", async () => {
  let getBadResults = async () => {
    await failingAsyncTest()
  }
  expect(await getBadResults()).toThrow()
})

But jest is just failing instead of passing the test:

 FAIL  src/failing-test.spec.js
  ● expects to have failed

    Failed: I should fail!

If I rewrite the test to looks like this:

expect(async () => {
  await failingAsyncTest()
}).toThrow()

I get this error instead of a passing test:

expect(function).toThrow(undefined)

Expected the function to throw an error.
But it didn't throw anything.
Rufi
  • 2,529
  • 1
  • 20
  • 41
Sean
  • 4,524
  • 2
  • 11
  • 12

10 Answers10

831

You can test your async function like this:

it('should test async errors', async () =>  {        
    await expect(failingAsyncTest())
    .rejects
    .toThrow('I should fail');
});

'I should fail' string will match any part of the error thrown.

Lisandro
  • 10,543
  • 1
  • 24
  • 29
  • 25
    This is now documented in https://facebook.github.io/jest/docs/en/expect.html#rejects – 0x24a537r9 Dec 19 '17 at 22:02
  • 11
    Actually has issues, documented example fails. https://github.com/facebook/jest/issues/3601 has workarounds including `await expect(failingAsyncTest()).rejects.toHaveProperty('message', 'I should fail');` – MrYellow Apr 06 '18 at 08:18
  • 3
    @Lisandro This code doesn't work. Yes, the unit test passes, but not because `failingAsyncTest` threw the right kind of error. It's more obvious if you change the implementation of `failingAsyncTest` to throw _the wrong error_ instead of the right one. (using Jest 23.6) – Tom Oct 15 '18 at 18:39
  • 4
    @Tom The solution never claims to match the error **Type**. It clearly states the string matches de error **Message**. It works perfectly fine. Best. – Lisandro Oct 20 '18 at 01:41
  • It's more obvious that toThrow tests for a substring if you change its parameter to a RegExp: `await expect(failingAsyncTest()).rejects.toThrow(/fail/);` – Dan Dascalescu Nov 19 '18 at 05:15
  • Make sure to add `await` or `return` before `expect`, otherwise the test will always pass even if it does not throw – DLight May 03 '21 at 15:17
  • 2
    What is the difference between expect(promise).rejects.toMatch(error) and expect(promise).rejects.toThrow(error). Shouldn't rejects unwrap the actual error? Then, this would not make sense (or doesn't make sense to me) -> expect(error).toThrow(error). There is an example for toMatch with rejects here: https://jestjs.io/docs/asynchronous#asyncawait – user3245268 Jun 28 '21 at 07:44
  • 1
    If anyone is interested, the updated documentation is here: https://jestjs.io/docs/expect#rejects – hem Aug 08 '22 at 13:28
  • I know you could be here if your async function takes input (params) and now how do you do that ```js expect(async () => await asyncFuncWithParam(params)).rejects.toThrow(MyError); ``` – Ashutosh Kumar Dec 23 '22 at 07:51
99

I'd like to just add on to this and say that the function you're testing must throw an actual Error object throw new Error(...). Jest does not seem to recognize if you just throw an expression like throw 'An error occurred!'.

Steve
  • 1,168
  • 7
  • 9
  • 34
    Well you just saved me a boatload of time. – Nick Jan 04 '21 at 15:53
  • 1
    is there any workaround if we are to keep `throw 'an error'`? – mikey Mar 25 '21 at 00:25
  • 1
    I am wrapping the function that should throw in a try catch in the tests. And then in the catch block I can do `expect(error).toMatchObject(expectedError)` but it looks quite dodgy – mikey Mar 25 '21 at 00:32
  • 1
    Apparently this also doesn't work with custom errors that inherit from Error :( – deckele Feb 24 '22 at 11:29
46
await expect(async () => { 
    await someAsyncFunction(someParams); 
}).rejects.toThrowError("Some error message");

We must wrap the code in a function to catch the error. Here we are expecting the Error message thrown from someAsyncFunction should be equal to "Some error message". We can call the exception handler also

await expect(async () => { 
    await someAsyncFunction(someParams); 
}).rejects.toThrowError(new InvalidArgumentError("Some error message"));

Read more https://jestjs.io/docs/expect#tothrowerror

Sumit Bopche
  • 658
  • 5
  • 9
  • 4
    You can do this more simply with `await expect(someAsyncFunction(someParams)).rejects.toThrowError(...)`. – Marc Sep 14 '22 at 13:30
  • 1
    @Marc if I don't add `async` inside, my test doesn't get any status - not a PASS and not a FAIL. The solution in this answer however, makes the test marked as PASS. – hipokito Sep 15 '22 at 13:12
  • `expect(() => someAsyncFunction())` may not work, but `expect(someAsyncFunction())` should work since the value passed to `expect()` in that case is a Promise. `expect()` will await and test its resolved value. – Erik J Jun 26 '23 at 20:45
24

Custom Error Class

The use of rejects.toThrow will not work for you. Instead, you can combine the rejects method with the toBeInstanceOf matcher to match the custom error that has been thrown.

Example

it("should test async errors", async () => {
  await expect(asyncFunctionWithCustomError()).rejects.toBeInstanceOf(
    CustomError
  )
})
Laode Muhammad Al Fatih
  • 3,994
  • 1
  • 18
  • 32
13

To be able to make many tests conditions without having to resolve the promise every time, this will also work:

it('throws an error when it is not possible to create an user', async () => {
        const throwingFunction = () => createUser(createUserPayload)

        // This is what prevents the test to succeed when the promise is resolved and not rejected
        expect.assertions(3)

        await throwingFunction().catch(error => {
            expect(error).toBeInstanceOf(Error)
            expect(error.message).toMatch(new RegExp('Could not create user'))
            expect(error).toMatchObject({
                details: new RegExp('Invalid payload provided'),
            })
        })
    })
Jonas Braga
  • 379
  • 4
  • 5
  • You can also write `throwingFunction.then(() => done('should have rejected'), error => { .... make assertions ... })`. ("done" is an argument to the spec callback.) – Coderer Aug 11 '23 at 19:28
6

I've been testing for Firebase cloud functions and this is what I came up with:

test("It should test async on failing cloud functions calls", async () => {
    await expect(async ()=> {
        await failingCloudFunction(params)
    })
    .rejects
    .toThrow("Invalid type"); // This is the value for my specific error
  });

This is built on top of lisandro's answer.

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Juan Giacosa
  • 183
  • 1
  • 12
  • This is the only code worked for me, but I completely don't understand why do I need to wrap a promise with a function to catch thrown error with Jest. It just feels it should work out of the box without it. – Igor Nikiforov Jun 07 '23 at 10:17
6

If you want to test that an async function does NOT throw:

it('async function does not throw', async () => {
    await expect(hopefullyDoesntThrow()).resolves.not.toThrow();
});

The above test will pass regardless of the value returned, even if undefined.

Keep in mind that if an async function throws an Error, its really coming back as a Promise Rejection in Node, not an error (thats why if you don't have try/catch blocks you will get an UnhandledPromiseRejectionWarning, slightly different than an error). So, like others have said, that is why you use either:

  1. .rejects and .resolves methods, or a
  2. try/catch block within your tests.

Reference: https://jestjs.io/docs/asynchronous#asyncawait

Dmitri R117
  • 2,502
  • 23
  • 20
3

This worked for me

it("expects to have failed", async () => {
  let getBadResults = async () => {
    await failingAsyncTest()
  }
  expect(getBadResults()).reject.toMatch('foo')
  // or in my case
  expect(getBadResults()).reject.toMatchObject({ message: 'foo' })
})
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    This answer deserves huge credit. Its the one that catches the cases such as `throw "some_string"` instead of `throw Error()` – damdafayton May 10 '23 at 08:46
-1

You can do like below if you want to use the try/catch method inside the test case.

test("some test case name with success", async () => {
 let response = null;
 let failure = null;
  // Before calling the method, make sure someAsyncFunction should be succeeded
 try {
  response = await someAsyncFunction();
 } catch(err) {
  error = err;
 }
expect(response).toEqual(SOME_MOCK_RESPONSE)
expect(error).toBeNull();
})

test("some test case name with failure", async () => {
 let response = null;
 let error = null;
 // Before calling the method, make sure someAsyncFunction should throw some error by mocking in proper way
 try {
  response = await someAsyncFunction();
 } catch(err) {
  error = err;
 }
expect(response).toBeNull();
expect(error).toEqual(YOUR_MOCK_ERROR)
})

Edit:

As my given solution is not taking the advantage of inbuilt jest tests with the throwing feature, please do follow the other solution suggested by @Lisandro https://stackoverflow.com/a/47887098/8988448

it('should test async errors', async () =>  {        
    await expect(failingAsyncTest())
    .rejects
    .toThrow('I should fail');
});
Gavara.Suneel
  • 458
  • 2
  • 14
-3

test("It should test async on failing cloud functions calls", async (done) => {
   failingCloudFunction(params).catch(e => {
    expect(e.message).toBe('Invalid type');
    done()
   })
  });
  • This test won't fail if `failingCloudFunction` does not throw an error. It only tests the error message. Not a good real life example. – Michal M. Mar 23 '22 at 11:57