![[備忘録] 同期関数/非同期関数で期待通りの例外がスローされたことをJestで評価する](https://devio2023-media.developers.io/wp-content/uploads/2019/07/jest_icon.png)
[備忘録] 同期関数/非同期関数で期待通りの例外がスローされたことをJestで評価する
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、CX事業本部 Delivery部の若槻です。
今回は、同期関数/非同期関数で期待通りの例外がスローされたことをJestで評価することがよくあるのですが、その際のテストの記述をよく忘れてはググっているので、備忘としてまとめておきます。
先にまとめ
同期関数/非同期関数の例外のスローは、それぞれ次の記述でテストできます。
test('_syncFunc', () => {
//同期関数のテスト
expect(() => syncFunc()).toThrow(new Error('syncFunc failed.'));
});
test('_asyncFunc', async () => {
//非同期関数のテスト
await expect(asyncFunc()).rejects.toThrow(new Error('asyncFunc failed.'));
});
解説
Jestで呼び出された関数が例外をスローしたことを評価したい場合は、.toThrow(error?)
を使用します。
Use
.toThrow
to test that a function throws when it is called.
また、JavaScriptの非同期関数は実行結果としてPromiseを返します。
Promiseがreject(例外をスロー)することを期待したテストを実施したい場合、Jestではrejects
マッチャーを使用します。
If you expect a promise to be rejected, use the .rejects matcher. It works analogically to the .resolves matcher. If the promise is fulfilled, the test will automatically fail.
検証
次の関数で例外がスローされていることをテストしたいとします。
export const syncFunc = () => {
subFunc();
throw new Error('syncFunc failed.');
};
export const asyncFunc = async () => {
subFunc();
throw new Error('asyncFunc failed.');
};
export const subFunc = () => {
console.log('hoge');
};
正しいパターン
まず、冒頭で示した、正しいパターンの記述によりテストが正常にPASSするパターンです。
import { syncFunc, asyncFunc, subFunc } from '../func';
jest.setTimeout(10000);
test('_syncFunc', () => {
(subFunc as jest.Mock) = jest.fn().mockReturnValue(void 0);
//同期関数のテスト - 正しいパターン
expect(() => syncFunc()).toThrow(new Error('syncFunc failed.'));
expect(subFunc).toBeCalledTimes(1);
});
test('_asyncFunc', async () => {
(subFunc as jest.Mock) = jest.fn().mockReturnValue(void 0);
//非同期関数のテスト - 正しいパターン
await expect(asyncFunc()).rejects.toThrow(new Error('asyncFunc failed.'));
expect(subFunc).toBeCalledTimes(1);
});
いずれのテストもPASSします。
$ npx jest
PASS test/func.test.ts (5.387 s)
✓ _syncFunc (5 ms)
✓ _asyncFunc (3002 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 5.444 s
Ran all test suites.
正しくないパターン
次に、よくやりがちな、正しくないパターンの記述によりテストがFAILするパターンです。
import { syncFunc, asyncFunc, subFunc } from '../func';
jest.setTimeout(10000);
test('_syncFunc', () => {
(subFunc as jest.Mock) = jest.fn().mockReturnValue(void 0);
//同期関数のテスト - 正くないパターン(無名関数となっていない)
expect(syncFunc()).toThrow(new Error('syncFunc failed.'));
expect(subFunc).toBeCalledTimes(1);
});
test('_asyncFunc', async () => {
(subFunc as jest.Mock) = jest.fn().mockReturnValue(void 0);
//非同期関数のテスト - 正しくないパターン(expectが同期呼び出しされていない)
expect(asyncFunc()).rejects.toThrow(new Error('asyncFunc failed.'));
expect(subFunc).toBeCalledTimes(1);
});
同期関数のテストを実行すると失敗しました。syncFunc
がスローした例外がマッチャー側でキャッチできず関数の実行自体がFAILしています。
$ npx jest -t "_syncFunc"
FAIL test/func.test.ts
✕ _syncFunc (1 ms)
○ skipped _asyncFunc
● _syncFunc
syncFunc failed.
1 | export const syncFunc = () => {
2 | subFunc();
> 3 | throw new Error('syncFunc failed.');
| ^
4 | };
5 |
6 | export const asyncFunc = async () => {
at syncFunc (func.ts:3:9)
at Object.<anonymous> (test/func.test.ts:9:18)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 skipped, 2 total
Snapshots: 0 total
Time: 2.248 s, estimated 3 s
Ran all test suites with tests matching "_syncFunc".
非同期関数のテストの実行も失敗しました。asyncFunc
の同期呼び出しが完了する前にsubFunc
の呼び出しが評価されており、テストがFAILしています。また非同期呼び出しされた関数の実行が完了する前にJestのテスト実行が終了しているため、Jest did not exit one second after the test run has completed.
というWarningが出ています。
$ npx jest -t "_asyncFunc"
FAIL test/func.test.ts
✕ _asyncFunc (2 ms)
○ skipped _syncFunc
● _asyncFunc
expect(jest.fn()).toBeCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
18 | expect(asyncFunc()).rejects.toThrow(new Error('asyncFunc failed.'));
19 |
> 20 | expect(subFunc).toBeCalledTimes(1);
| ^
21 | });
22 |
at Object.<anonymous> (test/func.test.ts:20:19)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 skipped, 2 total
Snapshots: 0 total
Time: 2.182 s, estimated 3 s
Ran all test suites with tests matching "_asyncFunc".
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
参考
以上