[Jest] throw Matcherで例外処理のテストを実装してみた
こんにちは、CX事業本部 IoT事業部の若槻です。
今回は、JavaScript Testing FrameworkであるJestで、例外処理のテストを実装してみました。
やってみた
Jestでは、.toThrow()というMactherが用意されており、これを使用して例外処理の評価を行うことができます。
toThrowError()
は.toThrow()
のエイリアスです。クラス、エラーメッセージ、インスタンスオブジェクトなどを評価対象として指定できます。
function drinkFlavor(flavor) { if (flavor == 'octopus') { throw new DisgustingFlavorError('yuck, octopus flavor'); } // Do some other stuff } test('throws on octopus', () => { function drinkOctopus() { drinkFlavor('octopus'); } // Test that the error message says "yuck" somewhere: these are equivalent expect(drinkOctopus).toThrowError(/yuck/); expect(drinkOctopus).toThrowError('yuck'); // Test the exact error message expect(drinkOctopus).toThrowError(/^yuck, octopus flavor$/); expect(drinkOctopus).toThrowError(new Error('yuck, octopus flavor')); // Test that we get a DisgustingFlavorError expect(drinkOctopus).toThrowError(DisgustingFlavorError); });
同期関数、非同期関数それぞれで.toThrow()
を実際に使ってみます。
同期関数の場合
まず、次のような同期関数(Sync Function)の場合。引数がhoge
の場合にError
をThrowします。
export const main = (arg: string): string => { try { if (arg === 'hoge') { throw new Error(); } return arg; } catch (e) { throw new Error(arg); } };
テストを成功させてみる
まず成功パターンです。上記の同期関数を対象としたテストを.toThrow()
で記述します。
import { main } from '../main'; describe('main', () => { test('toThrow with class', () => { expect(() => main('hoge')).toThrow(Error); }); test('toThrow with message', () => { expect(() => main('hoge')).toThrow('hoge'); }); test('toThrow with instance', () => { expect(() => main('hoge')).toThrow(new Error('hoge')); }); });
テストを実行すると、いずれのテストケースもパスさせられました。
npx jest main.test.ts PASS test/main.test.ts (7.023 s) main ✓ toThrow with class (4 ms) ✓ toThrow with message (1 ms) ✓ toThrow with instance Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 7.081 s Ran all test suites matching /main.test.ts/i.
テストを失敗させてみる
次に失敗パターンです。.toThrow()
の引数をそれぞれ先程と変更しています。
import { main } from '../main'; describe('main', () => { test('toThrow with class', () => { expect(() => main('hoge')).toThrow(TypeError); }); test('toThrow with message', () => { expect(() => main('hoge')).toThrow('nyao'); }); test('toThrow with instance', () => { expect(() => main('hoge')).toThrow(new Error('fuga')); }); });
テストを実行すると、いずれのテストケースもフェイルさせられました。
$ npx jest main.test.ts FAIL test/main.test.ts (5.774 s) main ✕ toThrow with class (16 ms) ✕ toThrow with message (2 ms) ✕ toThrow with instance (3 ms) ● main › toThrow with class expect(received).toThrow(expected) Expected constructor: TypeError Received constructor: Error Received message: "hoge" 6 | return arg; 7 | } catch (e) { > 8 | throw new Error(arg); | ^ 9 | } 10 | }; 11 | at Object.<anonymous>.exports.main (main.ts:8:11) at test/main.test.ts:5:18 at Object.<anonymous> (node_modules/expect/build/toThrowMatchers.js:83:11) at Object.throwingMatcher [as toThrow] (node_modules/expect/build/index.js:338:21) at Object.<anonymous> (test/main.test.ts:5:32) 3 | describe('main', () => { 4 | test('toThrow with class', () => { > 5 | expect(() => main('hoge')).toThrow(TypeError); | ^ 6 | }); 7 | 8 | test('toThrow with message', () => { at Object.<anonymous> (test/main.test.ts:5:32) ● main › toThrow with message expect(received).toThrow(expected) Expected substring: "nyao" Received message: "hoge" 6 | return arg; 7 | } catch (e) { > 8 | throw new Error(arg); | ^ 9 | } 10 | }; 11 | at Object.<anonymous>.exports.main (main.ts:8:11) at test/main.test.ts:9:18 at Object.<anonymous> (node_modules/expect/build/toThrowMatchers.js:83:11) at Object.throwingMatcher [as toThrow] (node_modules/expect/build/index.js:338:21) at Object.<anonymous> (test/main.test.ts:9:32) 7 | 8 | test('toThrow with message', () => { > 9 | expect(() => main('hoge')).toThrow('nyao'); | ^ 10 | }); 11 | 12 | test('toThrow with instance', () => { at Object.<anonymous> (test/main.test.ts:9:32) ● main › toThrow with instance expect(received).toThrow(expected) Expected message: "fuga" Received message: "hoge" 6 | return arg; 7 | } catch (e) { > 8 | throw new Error(arg); | ^ 9 | } 10 | }; 11 | at Object.<anonymous>.exports.main (main.ts:8:11) at test/main.test.ts:13:18 at Object.<anonymous> (node_modules/expect/build/toThrowMatchers.js:83:11) at Object.throwingMatcher [as toThrow] (node_modules/expect/build/index.js:338:21) at Object.<anonymous> (test/main.test.ts:13:32) 11 | 12 | test('toThrow with instance', () => { > 13 | expect(() => main('hoge')).toThrow(new Error('fuga')); | ^ 14 | }); 15 | }); 16 | at Object.<anonymous> (test/main.test.ts:13:32) Test Suites: 1 failed, 1 total Tests: 3 failed, 3 total Snapshots: 0 total Time: 5.834 s, estimated 6 s Ran all test suites matching /main.test.ts/i.
非同期関数の場合
続いて、次のような非同期関数(Async Function)の場合。
export const main = async (arg: string): Promise<string> => { try { if (arg === 'hoge') { throw new Error(); } return arg; } catch (e) { throw new Error(arg); } };
非同期関数の場合も使い方は同じとなりますが、Promiseを解決できるように.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.
import { main } from '../async_main'; describe('main', () => { test('Error toThrow', () => { expect(() => main('hoge')).rejects.toThrow(Error); }); });
テストを実行すると、パスさせられました。
$ npx jest async_main.test.ts PASS test/async_main.test.ts main ✓ Error toThrow (3 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 4.845 s, estimated 6 s Ran all test suites matching /async_main.test.ts/i.
おわりに
JavaScript Testing FrameworkであるJestで、例外処理のテストをしてみました。
正常系のテストは実装するかと思いますが、エラー系のテストは面倒くさがって省いてしまうこともあると思います。しかし今回紹介したJestの.throw
を使えば簡単に実装できるので活用していきたいですね。
以上