Jest test fails when trying to test an asynchronous function throws
In your first test:
return getKmlFilename(createEmptyFolder())
.catch(e => expect(e).toMatch('No valid KML file in'));
It would not complain if the Promise resolves.
In the second test
try {
const result = await getKmlFilename(createEmptyFolder());
} catch (e) {
...
}
It also would not complain if the Promise resolves as it would not get into the catch block.
To test Promises, ask yourself these questions:
- Should the promise succeed (resolve) or fail (reject)?
- Is your result or rejection value an
Error
or a regular object?
In jest, you should be able to do this:
- Resolve to regular object:
expect(yourThing()).resolves.toMatchSnapshot()
- Resolve to error (never seen that):
expect(yourThing()).resolves.toThrow(/something/)
- Rejects to an error:
expect(yourThing()).rejects.toThrow(/something/)
- Rejects to a regular object (are you sure you want this?):
expect(yourThing()).rejects.toMatchSnapshot()
Be aware that an async function always returns a value (a Promise object), so the "usual" expect(() => yourThing()).toThrow()
will not work. You need to wait for the result of the Promise first (by using resolves
or rejects
) and then testing it.
Related videos on Youtube
Comments
-
FLC over 1 year
I have an asynchronous function and I want to test for both: success and failure. On success the function returns a string, on failure it throws. I'm failing miserably at testing failure. Here's my code:
- getKmlFileName.test.js
I have disabled by commenting the code that fails and added the results in comments
'use strict'; const path = require('path'); const fs = require('fs'); const getKmlFilename = require('./getKmlFileName.js'); const createGoodFolder = () => { const folderPath = fs.mkdtempSync('/tmp/test-getKmlFilename-'); const fileDescriptor = fs.openSync(path.join(folderPath, 'doc.kml'), 'w'); fs.closeSync(fileDescriptor); return folderPath; }; const createEmptyFolder = () => fs.mkdtempSync('/tmp/test-getKmlFilename-'); describe('/app/lib/getKmlFilename', () => { // Success tests test('Should return a KML filename', async () => { const result = await getKmlFilename(createGoodFolder()); expect(result).toMatch(/\.kml$/); }); // Failure tests test('Should throw if no KML files in folder', () => { // Expected one assertion to be called but received zero assertion calls. // expect.assertions(1); // expect(function).toThrow(undefined) // Received value must be a function, but instead "object" was found //return getKmlFilename(createEmptyFolder()) // .catch(e => expect(e).toThrow()); // expect(string)[.not].toMatch(expected) // string value must be a string. // Received: // object: // [Error: No valid KML file in /tmp/test-getKmlFilename-j2XxQ4] return getKmlFilename(createEmptyFolder()) .catch(e => expect(e).toMatch('No valid KML file in')); }); test('Should throw if no KML files in folder - try/catch version', async () => { // Expected one assertion to be called but received zero assertion calls. // expect.assertions(1); try { const result = await getKmlFilename(createEmptyFolder()); } catch (e) { // Received value must be a function, but instead "object" was found // expect(e).toThrow(); // expect(string)[.not].toMatch(expected) // string value must be a string. // Received: // object: // [Error: No valid KML file in /tmp/test-getKmlFilename-3JOUAX] expect(e).toMatch('No valid KML file in'); } }); });
As you can see, nothing works. I believe my tests are almost an exact copy of Promises example for the first failure test and Async/Await example for the last one, however none works.
I believe the difference with the examples from Jest documentation is that they show how to test a function
throw
s and how to test a Promise thatreject
s. But my promise rejects by throwing.Checking the function in the node console I get this log:
// import function > getKml = require('./getKmlFileName.js') [AsyncFunction: getKmlFilename] // trying it with a proper folder shows we get a Promise > getKml('/tmp/con') Promise { <pending>, domain: Domain { domain: null, _events: { error: [Function: debugDomainError] }, _eventsCount: 1, _maxListeners: undefined, members: [] } } // trying it with a failing folder shows it's a rejected promise which throws > getKml('/tmp/sin') Promise { <pending>, domain: Domain { domain: null, _events: { error: [Function: debugDomainError] }, _eventsCount: 1, _maxListeners: undefined, members: [] } } > (node:10711) UnhandledPromiseRejectionWarning: Error: No valid KML file in /tmp/sin at getKmlFilename (/home/flc/soft/learning/2018.06.08,jest/getKmlFileName.js:14:11) at <anonymous> at process._tickDomainCallback (internal/process/next_tick.js:228:7) (node:10711) 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 .catch(). (rejection id: 1) (node:10711) [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.
As you can see from the inlined comments, the function is doing what it should, however I don't know how to test this in Jest. Any help would be very much appreciated.
I case the code here looks too convoluted, I prepared a repository which contains my misfortunes learning Jest
UPDATE 2018.06.12:
Somehow my message got scrambled and lost the first part which was the actual code I'm trying to test, my apologies for that, here it is:
-
getKmlFileName.js
'use strict'; const globby = require('globby'); const path = require('path'); const getKmlFilename = async (workDir) => { const pattern = path.join(workDir, '**/*.kml'); const files = await globby(pattern); if (files && files.length > 0) { // Return first KML found, if there are others (improbable), ignore them return path.basename(files[0]); } else { throw new Error(`No valid KML file in ${workDir}`); } }; module.exports = getKmlFilename;
-
FLC almost 6 yearsThe failing tests are the ones that test for failure, the succeding test works fine, is the first one. To answer your points: 1) Both, first test tests success, the other two are two different ways I found in the docs to tests async code. 2) Success result is a string, rejection result is an
Error
as you can see in my update. My doubt is: will Javascriptasync
take care of wrapping thethrow
in areject
call? I thought it does. Finally, I'm trying to test for failure, so I don't expect promise to resolve successfully in my later tests. Whyexpect.assertions(1)
doesn't work? -
FLC almost 6 yearsFinally got a chance to continue with this, the third way was what I needed to test a promise that throws. Thank you.
-
Árpád Magosányi over 3 yearsI guess all of us came for the 3rd way. Could you make that part of your excellent answer more pronounced?
-
Marc about 3 yearsIt doesn't look like jest respects parameters here
rejects.toThrow(.....)
. No matter what I pass it accepts it and passes the test. Also if the promise resolves thenexpect(myPromise()).rejects.toThrow()
will still pass the test but complain ofUnhandledPromiseRejectionWarning: Error: expect(received).rejects.toThrow() Received promise resolved instead of rejected
-
Marc about 3 yearsPlease change your 3rd example to
await expect(yourThing()).rejects.toThrow(/yourErrorMessage/)
-
Narigo about 3 yearsWould it be clear enough if I changed number 3 to
expect(yourThing()).rejects.toThrow(/your error message as regular expression match/)
? -
Mike 'Pomax' Kamermans over 2 yearsBut why do we need the
.resolves.
and.rejects.
in there? Because the whole point ofawait
is that it wraps a Promise such that it either yields data like any sync function, or throws an error. -
Narigo over 2 yearsIf you
await
the promise and useexpect
on the result, that should work as you said - as the thrown error should be caught by the async context. If you're testing a rejected promise, you need the expectation in the catch block but you also need to make sure the promise doesn't accidentally resolve. I guess that's why.rejects.
exists then