Why is the Promise reject() in my jest.mock going to a then() rather than a catch()?

梦想的初衷 提交于 2019-12-19 18:28:53

问题


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?


回答1:


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
});



回答2:


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:

  1. If you return a value from them, the promise then/catch created is resolved with that value.

  2. 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).

  3. 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), or

  • You 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!