I am stucking for 3 hours on this topic. I dont find a solution how to test the if(err) branch in this code:
function createFile(data){
return new Promise(function(resolve, reject) {
try {
if(data === null || data === undefined){
throw new Error(errorMessages.noDataDefined);
}
let internalJobId = uuid.v4();
let fileName = 'project_name' + internalJobId + '.xml';
fs.writeFile(config.tmpPath + fileName, data, function (err) {
if (err){
throw new Error(err.toString());
} else {
resolve(fileName);
}
});
} catch (error) {
return reject(error);
}
});
}
This test passes but it does not call the if (err) { throw new Error(err.toString())}
I have to find a solution, how the callback returns an error, but I dont get the right solution.
test('Error', () => {
jest.mock('fs', () => ({
writeFile: jest.fn((path, data, callback) => callback(Error('some error')))
}));
return expect(createFile('Does not matter')).rejects.toThrow('some error');
});
But with this test there is even not a reject, so there is never thrown an error. I would appreciate if anyone could help me out there.
There are two problems here. One is that fs.writeFile
isn't correctly mocked. Another is that createFile
doesn't correctly handle errors and can't meet the expectation.
jest.mock
affects modules that haven't been imported yet and hoisted to the top of the block (or above imports when used at top level). It cannot affect fs
if a module that uses it has already been imported. Since fs
functions are commonly used with their namespace, they can also be mocked as methods.
It should be either:
// at top level
import fs from 'fs';
jest.mock('fs', ...);
...
Or:
// inside test
jest.spyOn(fs, 'writeFile').mockImplementation(...);
...
And be asserted to make the test more specific:
expect(fs.writeFile).toBeCalledTimes(1);
expect(fs.writeFile).toBeCalledWith(...);
return expect(createFile('Does not matter'))...
Promise constructor doesn't need try..catch
because it already catches all synchronous error inside it and cannot catch asynchronous errors from callbacks. For places where a promise needs to be rejected, reject
can be preferred for consistency.
That an error is thrown inside fs.writeFile
callback is a mistake and results in pending promise. It has no chance to reject the promise, has no chance to be caught with try..catch
outside the callback and causes uncaught error.
It should be:
function createFile(data){
return new Promise(function(resolve, reject) {
if(data === null || data === undefined){
reject(new Error(errorMessages.noDataDefined));
}
let internalJobId = uuid.v4();
let fileName = 'project_name' + internalJobId + '.xml';
fs.writeFile(config.tmpPath + fileName, data, function (err) {
if (err){
reject(new Error(err.toString()); // reject(err) ?
} else {
resolve(fileName);
}
});
});
}
In order to keep nesting to minimum, parts that don't need promisification can be moved outside the constructor with the function being async
:
async function createFile(data){
if(data === null || data === undefined){
throw new Error(errorMessages.noDataDefined);
}
return new Promise(function(resolve, reject) {
let internalJobId = uuid.v4();
...
There is also fs.promises
API that may not need to be promisified.
Also notice that new Error(err.toString())
may be unnecessary and result in unexpected error message and so fail the assertion. The promise can be rejected with err
as is. If the purpose is to remove unnecessary error information or change error stack, it should be new Error(err.message)
.
The solution was:
jest.spyOn(fs, 'writeFile').mockImplementation((f, d, callback) => {
callback('some error');
});
Thanks to Estus Flask!
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