Jest: Async testing fails with async/await

Created on 28 Nov 2019  路  9Comments  路  Source: facebook/jest

馃悰 Bug Report

Asynchronous testing does not support async functions.

To Reproduce

await expect(async () => {
   throw new Error('Test');
}).rejects.toThrow('Test');

Yields:

    expect(received).rejects.toThrow()

    Matcher error: received value must be a promise

    Received has type:  function
    Received has value: [Function anonymous]

      22 |       await expect(async () => {
      23 |          throw new Error('Test');
    > 24 |       }).rejects.toThrow('Test');
         |                  ^
      25 |    });
      26 | 
      27 | 

Expected behavior

The test should treat the function as a promise. At the moment, the fix is to chain the function to a Promise, for example:

await expect( Promise.resolve().then(async () => {
   throw new Error('Test');
})).rejects.toThrow('Test');

The solution is quite easy, instead of expecting only an object, expect should check if the constructor is an AsyncFunction. For example :

if (value.constructor.name === 'Promise` || value.constructor.name === 'AsyncFunction') {
    ...
}

envinfo

  System:
    OS: Linux 5.3 Ubuntu 19.10 (Eoan Ermine)
    CPU: (16) x64 AMD Ryzen 7 PRO 2700 Eight-Core Processor
  Binaries:
    Node: 12.10.0 - ~/.nvm/versions/node/v12.10.0/bin/node
    Yarn: 1.19.1 - ~/.nvm/versions/node/v12.10.0/bin/yarn
    npm: 6.11.3 - ~/.nvm/versions/node/v12.10.0/bin/npm
  npmPackages:
    jest: ^24.9.0 => 24.9.0 
Feature Request Help Wanted good first issue

Most helpful comment

Still exists for resolves.

Current behavior

await expect(async () => new Promise(resolve => setTimeout(resolve, 1000))).resolves.toBeUndefined()

// Matcher error: received value must be a promise

Workaround

await expect((async () => new Promise(resolve => setTimeout(resolve, 1000)))()).resolves.toBeUndefined()

All 9 comments

Hello 馃憢

Your suggestion would indeed make the API more practical for the use case you present, but it would not work consistently. Relying on the name of the function being passed is error-prone. Indeed, it is not necessarily an arrow-function and it could be a named async function.

I also don't believe it is a bug. The documentation makes no claim to support functions; it knowingly only supports promises.

To work around this, you can simply call the function.
This is similar to your suggestion, but you don't really need the Promise.resolve. The code below will work just as well:

await expect((async () => {
   throw new Error('Test');
})()).rejects.toThrow('Test');

Not only does this work fine, but most of the time it is not even needed. Indeed, people tend to test _one_ function call. Say I already have an async function called myfunction. This is how I would test it:

await expect(myfunction()).rejects.toThrow('Test'));

It doesn't look too bad.

I think the syntax of await expect(myfunction()).rejects.toThrow('Test')); is totally reasonable.

However, I also ran in to this situation, where it seemed inconsistent compared to the non-async case, where jest was smart enough to know it needed to invoke the supplied function due to the matchers that were chained on:

const fnToTest = (num) => {
  throw new Error('Test error ${num}`);
}
// calls the wrapper lambda for me due to the `toThrow` matcher
expect(() => fnToTest(1)).toThrow('Test error');

vs

const fnToTest = async (num) => {
  throw new Error('Test error ${num}`);
}
// doesn't call the wrapper lambda for me, though it seems like we could if we detect the parameter to `expect` was a function rather than a promise
expect(() => fnToTest(2)).rejects.toThrow('Test error');

So, while I agree that you can achieve the desired test pretty cleanly with the current way of doing things, the inconsistency with the non-async case seems like a gotcha for people learning the library that could be addressed by making jest a little smarter (and we wouldn't have to rely on function names with this solution).

I'll happily merge a PR which invokes the provided function if it's not a promise, and throws if the return value of that function is not a promise. Anyone up for it? 馃檪

@SimenB Can I take a look into this?

Yeah, that'd be wonderful. Go for it!

@udaypydi Any update on your progress thus far? Otherwise, I might try to have a go as well :)

@tanettrimas I am working on it. Will raise a PR by tomorrow end of the day.
Thanks!

I like to take a look into this

Still exists for resolves.

Current behavior

await expect(async () => new Promise(resolve => setTimeout(resolve, 1000))).resolves.toBeUndefined()

// Matcher error: received value must be a promise

Workaround

await expect((async () => new Promise(resolve => setTimeout(resolve, 1000)))()).resolves.toBeUndefined()
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ianp picture ianp  路  3Comments

withinboredom picture withinboredom  路  3Comments

jardakotesovec picture jardakotesovec  路  3Comments

Secretmapper picture Secretmapper  路  3Comments

paularmstrong picture paularmstrong  路  3Comments