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:
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 that reject
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;
It's common in JavaScript for code to run asynchronously. When you have code that runs asynchronously, Jest needs to know when the code it is testing has completed, before it can move on to another test.
Just return a promise from your test, and Jest will wait for that promise to resolve. If the promise is rejected, the test will automatically fail. For example, let's say that fetchData , instead of using a callback, returns a promise that is supposed to resolve to the string 'peanut butter' .
Jest waits until the done callback is called before finishing the test. test('the data will be peanut butter', done => { function callback(data) { expect(data). toBe('peanut butter'); done(); } fetchData(callback); }); In the case where done() is never called, the test fails, which is exactly what you want to happen.
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:
Error
or a regular object?In jest, you should be able to do this:
expect(yourThing()).resolves.toMatchSnapshot()
expect(yourThing()).resolves.toThrow(/something/)
expect(yourThing()).rejects.toThrow(/something/)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With