Asynchronous testing does not support async functions.
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 |
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') {
...
}
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
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.
await expect(async () => new Promise(resolve => setTimeout(resolve, 1000))).resolves.toBeUndefined()
// Matcher error: received value must be a promise
await expect((async () => new Promise(resolve => setTimeout(resolve, 1000)))()).resolves.toBeUndefined()
Most helpful comment
Still exists for
resolves.Current behavior
Workaround