I have two files, getItemInfo.js
to make API calls and getItemInfo.test.js
which is the respective Jest test file.
On the test file, I am mocking the http calling triggered by node module request-promise
.
The question is on the second code block, surrounded by *********
's.
Basically why is the reject()
error still going to a then()
block in the second unit test?
// getItemInfo.js
const rp = require('request-promise');
const getItemInfo = (id) => {
const root = 'https://jsonplaceholder.typicode.com/posts/';
const requestOptions = {
uri: `${root}/${id}`,
method: 'GET',
json: true
}
return rp(requestOptions)
.then((result) => {
return result;
})
.catch((err) => {
return err;
});
};
module.exports = {
getItemInfo: getItemInfo
};
And here is my Jest unit test file.
// getItemInfo.test.js
const ItemService = require('./getItemInfo');
jest.mock('request-promise', () => (options) => {
const id = Number.parseInt(options.uri.substring(options.uri.lastIndexOf('/') + 1));
return new Promise((resolve, reject) => {
if (id === 12) {
return resolve({
id: id,
userId: 1,
title: '',
body: ''
});
} else {
return reject('something went wrong'); // <-- HERE IS THE REJECT
}
})
});
describe('getItemInfo', () => {
it('can pass', done => {
const TEST_ID = 12
ItemService.getItemInfo(TEST_ID).then((result) => {
console.log('result:',result);
expect(result.id).toBe(TEST_ID);
expect(result.userId).toBeDefined();
expect(result.title).toBeDefined();
expect(result.body).toBeDefined();
done();
});
});
it('can fail', (done) => {
const TEST_ID = 13;
ItemService.getItemInfo(TEST_ID)
.catch((err) => {
// *************
// This "catch" block never runs
// even if the jest.mock above Promise.rejects
// Why is that???
// *************
console.log('catch():', err);
done();
})
.then((result) => {
// this block runs instead.
// and it returns "then: something went wrong"
console.log('then():', result);
done();
});
});
});
This is the unit test's output. The command is simply jest
. The last line should be run from the catch()
statement, not the then()
:
PASS ./getItemInfo.test.js
getItemInfo
✓ can pass (9ms)
✓ can fail (1ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 0.703s, estimated 1s
Ran all test suites.
----------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
----------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
getItemInfo.js | 100 | 100 | 100 | 100 | |
----------------|----------|----------|----------|----------|----------------|
console.log getItemInfo.test.js:25
result: { id: 12, userId: 1, title: '', body: '' }
console.log getItemInfo.test.js:48
then(): something went wrong
What am I doing wrong?
Normally, such . catch doesn't trigger at all. But if any of the promises above rejects (a network problem or invalid json or whatever), then it would catch it.
You can chain promises with await if you choose to. function generateException() { return new Promise(reject => { return reject(new Error('Promise Rejected'); }) it('should give an error', async ()=> { await generateException(). catch(error => { expect(error. message).to.
If a Promise does not resolve and no rejection callback is provided in it, it skips forward to the next . then in the chain with a rejection callback in it. The rejection callback is the reject(err) . For more detailed explanations see: Javascript Promises - There and Back again.
Return a promise from your test, and Jest will wait for that promise to resolve. If the promise is rejected, the test will fail.
Why is the Promise reject() in my jest.mock going to a then() rather than a catch()?
Your .catch()
handler is converting a rejected promise into a resolved promise and thus only the outer .then()
handler is called.
When you use .catch()
like this:
.catch((err) => {
return err;
});
and do not rethrow the error or return a rejected promise, then the rejection is considered "handled" and the returned promise becomes resolved, not rejected. This is just like using a try/catch. In a catch handler, the exception is handled unless you throw it again.
You can see that in this simple snippet:
new Promise((resolve, reject) => {
reject(new Error("reject 1"));
}).catch(err => {
// returning a normal value here (anything other than a rejected promise)
// causes the promise chain to flip to resolved
return err;
}).then(val => {
console.log("Promise is now resolved, not rejected");
}).catch(err => {
console.log("Don't get here");
});
There's really no reason for either of these:
.then((result) => {
return result;
})
.catch((err) => {
return err;
});
You can just remove both of them. The .then()
handler is just superfluous code and the .catch()
handler eats the rejection and turns it into a resolved promise.
If you want to keep the .catch()
handler, but allow the rejection to propagate upwards, then you need to rethrow.
.catch((err) => {
console.log(err);
throw err; // keep promise rejected so reject will propagate upwards
});
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