最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

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

programmeradmin1浏览0评论

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 = '/';
    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?

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?

Share Improve this question asked Aug 8, 2017 at 6:03 Kevin HernandezKevin Hernandez 5251 gold badge6 silver badges20 bronze badges 1
  • 1 Because .catch((err) => { return err; }); handles the error! You probably don't want to do that. – Bergi Commented Aug 8, 2017 at 6:14
Add a comment  | 

2 Answers 2

Reset to default 13

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:

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

  2. If you return a thenable from them, the promise then/catch created is resolved to that thenable (fulfilled or rejected based on what that thenable does).

  3. If you throw within them, the promise then/catch created is rejected with that error.

(FWIW, I go into promise terminology — "fulfill" vs. "resolve," etc. — in this post on my blog.)

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.

发布评论

评论列表(0)

  1. 暂无评论