web-dev-qa-db-ja.com

jest.mockのPromisereject()がcatch()ではなくthen()に移動するのはなぜですか?

API呼び出しを行うための_getItemInfo.js_と、それぞれのJestテストファイルである_getItemInfo.test.js_の2つのファイルがあります。

テストファイルで、ノードモジュール_request-promise_によってトリガーされたhttp呼び出しをモックしています。

質問は、_*********_で囲まれた2番目のコードブロックにあります。基本的に、2番目の単体テストでreject()エラーがまだthen()ブロックに送られるのはなぜですか?

_// 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
};
_

そして、これが私のJestユニットテストファイルです。

_// 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();
        });
    });
});
_

これはユニットテストの出力です。コマンドは単にjestです。最後の行は、catch()ではなく、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
_

私は何が間違っているのですか?

6
Kevin Hernandez

Jest.mockのPromisereject()がcatch()ではなくthen()に移動するのはなぜですか?

.catch()ハンドラーは、拒否されたPromiseを解決済みのPromiseに変換しているため、外側の.then()ハンドラーのみが呼び出されます。

このように.catch()を使用する場合:

_.catch((err) => {
    return err;
});
_

エラーを再スローしたり、拒否されたPromiseを返さなかったりすると、拒否は「処理済み」と見なされ、返されたPromiseは解決され、拒否されません。これは、try/catchを使用するのと同じです。キャッチハンドラでは、例外を再度スローしない限り、例外が処理されます。

この単純なスニペットでそれを見ることができます:

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

これらのいずれにも理由はありません。

_.then((result) => {
    return result;
})
.catch((err) => {
    return err;
});
_

両方を削除するだけです。 .then()ハンドラーは単なる不要なコードであり、.catch()ハンドラーは拒否を食い止め、解決されたpromiseに変換します。

.catch()ハンドラーを保持したいが、拒否が上方に伝播することを許可したい場合は、再スローする必要があります。

_.catch((err) => {
    console.log(err);
    throw err;       // keep promise rejected so reject will propagate upwards
});
_
9
jfriend00

拒否を解決策に変換したため:

.catch((err) => {
    return err;
});

拒否をgetItemInfoから伝播させたい場合は、catchハンドラーをgetItemInfoから削除します。

catchthenはどちらもpromiseを作成して返し、それらのハンドラーは同じように扱われることに注意してください。

  1. returnそれらからの値の場合、作成されたpromise then/catchはその値で解決されます。

  2. return thenableからのthenableの場合、作成されたpromise then/catchはそのthenableにスレーブされます(thenableの機能に基づいて解決または拒否されます)。

  3. それらの中でthrowの場合、作成されたpromise then/catchはそのエラーで拒否されます。

次の場合にのみ、catchハンドラーが必要です。

  • チェーンを他の何か(getItemInfoにあります)に渡していない、または

  • エラーを解決(リカバリ)に変換するか、別のエラーに変換するなど、何らかの方法でエラーを変換する必要があります。後者を行うには、throwするか、拒否される、または拒否される予定の約束を返します。

2
T.J. Crowder