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?
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
});
Because you've converted the rejection into a resolution:
.catch((err) => {
return err;
});
If you want the rejection to propagate out of getItemInfo
, remove the catch
handler from getItemInfo
.
Remember that catch
and then
both create and return promises, and their handlers are treated the same:
If you return a value from them, the promise
then
/catch
created is resolved with that value.If you return a thenable from them, the promise
then
/catch
created is slaved to that thenable (resolved or rejected based on what that thenable does).If you throw within them, the promise
then
/catch
created is rejected with that error.
You only need a catch
handler if:
You're not passing the chain along to something else (which you are in
getItemInfo
), orYou need to transform an error in some way, either by transforming it into a resolution (recovery) or transforming it into a different error. To do the latter you
throw
or return a promise that is or will be rejected.
来源:https://stackoverflow.com/questions/45560398/why-is-the-promise-reject-in-my-jest-mock-going-to-a-then-rather-than-a-catc