33

I am testing an endpoint with JEST and Got. I expect 403 Forbidden error. The following code prints the error from catch block AND fails that the identical call does not throw an error. Why?

    try {
        response = await api(`verify/${profile.auth.verifyToken}`, {method: 'POST'}).json();
    } catch (e) {
        console.log(e);
    }
    expect(async () => {
        response = await api(`verify/${profile.auth.verifyToken}`, {method: 'POST'}).json();
    }).toThrow();

Output:

console.log test/api.int.test.js:112
HTTPError: Response code 403 (Forbidden)
    at EventEmitter.<anonymous> (C:\dev\mezinamiridici\infrastructure\node_modules\got\dist\source\as-promise.js:118:31)
    at processTicksAndRejections (internal/process/task_queues.js:97:5) {
  name: 'HTTPError'
}


Error: expect(received).toThrow()
Received function did not throw

This variant does not work either:

expect(() => api(`verify/${profile.auth.verifyToken}`, {method: 'POST'})).toThrow();

Btw when the HTTPError is thrown and not catched, there is no stacktrace and I do not see where the error was thrown. If there are other error I exactly see which test line was responsible. Why?

SuperStormer
  • 4,997
  • 5
  • 25
  • 35
Leos Literak
  • 8,805
  • 19
  • 81
  • 156

1 Answers1

83

expect(...).toThrow() is for checking if an error is thrown from a function call. When calling an async function, it never throws an error; rather it returns a Promise which may eventually become "rejected." Although async functions use the same throw/catch terminology, the code required to detect a thrown error differs from what's required to detect a rejected Promise. This is why Jest needs a different assertion technique.

Try expect(...).rejects.toThrow() instead:

await expect(() => api(`verify/${profile.auth.verifyToken}`, {method: 'POST'}).json())
  .rejects.toThrow();

Notice you have to await this assertion because Jest needs to wait until the Promise finalizes before seeing whether it resolved or rejected.

Jacob
  • 77,566
  • 24
  • 149
  • 228
  • 1
    I used successfuly expect(api(`verify/${profile.auth.verifyToken}`, {method: 'POST'})).rejects.toThrow(); – Leos Literak Apr 14 '20 at 18:39
  • Your answer helped me to overcome this trouble but I still do not understand why it did not work. My code had await keyword so there was no waiting promise and it threw the HttpError. So IMHO it should catch it, should not it? – Leos Literak Apr 15 '20 at 08:21
  • 4
    `async`/`await` are syntactic sugar around Promises. From the outside, the result of calling an async function is the same as calling a function that returns a Promise object. The `toThrow` method doesn't inspect returned Promise objects; it's instead checking if calling the function causes a synchronous error to be thrown. Since detecting rejected Promises requires different logic, the Jest team opted to make a different API method for checking those. – Jacob Apr 15 '20 at 14:57