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
_
私は何が間違っているのですか?
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
});
_
拒否を解決策に変換したため:
.catch((err) => {
return err;
});
拒否をgetItemInfo
から伝播させたい場合は、catch
ハンドラーをgetItemInfo
から削除します。
catch
とthen
はどちらもpromiseを作成して返し、それらのハンドラーは同じように扱われることに注意してください。
returnそれらからの値の場合、作成されたpromise then
/catch
はその値で解決されます。
return thenableからのthenableの場合、作成されたpromise then
/catch
はそのthenableにスレーブされます(thenableの機能に基づいて解決または拒否されます)。
それらの中でthrowの場合、作成されたpromise then
/catch
はそのエラーで拒否されます。
次の場合にのみ、catch
ハンドラーが必要です。
チェーンを他の何か(getItemInfo
にあります)に渡していない、または
エラーを解決(リカバリ)に変換するか、別のエラーに変換するなど、何らかの方法でエラーを変換する必要があります。後者を行うには、throw
するか、拒否される、または拒否される予定の約束を返します。