任意のブラウザ上でJestで書いたテストを実行する
フロントエンドエンジニアの今村です。任意のブラウザ上でJestで書いたテストを実行する方法を紹介します。
TL;DR
- Jestのテストランナーを任意のブラウザを対象に実行するのは難しい
- Jestのモックやアサーションの機能はブラウザ環境で実行可能
-> 「ブラウザ向けのテストランナー(たとえばKarma) + Jestのテストコード」の組み合わせならブラウザ上で実行できる
Jestとは
JavaScriptの世界で現在もっとも人気がありそうなテスティングフレームワークです。次のような特徴があります。
- 早い
- ブラウザ向けのコードのテストでもブラウザを起動せず、JSDOMというNode.js上に仮想的に用意したブラウザ環境を使う
- テストを並列に実行できる
- watchモードではコード変更時に関連するテストだけを実行する
- オールインワン
- テストランナー
- アサーション
- モック
- カバレッジ
- スナップショットテスト
- HTMLのレンダリング結果をスナップショットとして保存し、UIの予期せぬ変更がないか確認するためのテスト
- StorybookのアドオンのStoryShotsも、Jestを利用してスナップショットテストの機能を提供している
Jestのデメリット
Jestの最大のデメリットの一つは、ブラウザ向けのコードのテストを任意のブラウザ上で実行できないことでしょう。
関連するissue: https://github.com/facebook/jest/issues/848
先に述べたとおり、テストは通常JSDOM環境で実行されます。
公式ドキュメントにpuppeteerで動かす方法が載っていたり、Electronで動かすためのカスタムランナーが開発されていたりしますが、「任意のブラウザ」とまではいきません。コードの中に(たとえば)IEでのみ発生するバグが潜んでいないか、Jestによるユニットテストで検出することはできないのでしょうか。
ソリューション
- この頃流行りのJestを使いたい
- 任意のブラウザ上でテストを実行できなければ精神の安寧が得られない
相反する思いにさいなまれながら上記のissueを眺めていた私の目に、次のコメントが飛び込んできました。
https://github.com/facebook/jest/issues/848#issuecomment-324921548
Jestの機能のうちアサーションを担うexpect
と、モックを担うjest-mock
は、すでにブラウザ上で動くようになっているというのです。つまり、Jestで書いたテストコードそのものはブラウザ上で問題なく動くはず、ということです。現時点で任意のブラウザで動かすのが難しいのはテストランナーですが、これはKarmaでも使ってみましょう。
というわけで、試してみたのが以下です。
https://github.com/kimamula/jest-karma-angular-demo
仕事で普段Angularを使っているので、Angularプロジェクトで試しています。このリポジトリからコードをcloneすると、Jestで書いた同一のテストコードをJestのテストランナーでもKarmaでも動かせるのを確認できます。
yarn test
-> Jestのテストランナーによるテストyarn karma
-> Karmaによるテスト
キモは、Karmaからテストを実行する際のエントリーポイントとなるファイルで、次の処理を追記することです。
// https://github.com/kimamula/jest-karma-angular-demo/blob/master/src/test.ts
window['jest'] = require('jest-mock');
window['expect'] = require('expect');
Karmaの設定はJasmineを動かす時と同様にしてしまえばよいです(JestのテストランナーがデフォルトでJamineベースのものなため、Jasmineと同じ設定で実行できるようです)。
いくつか細かい話はありますが(※)、基本的にはこれだけです。
何が嬉しいの?
以下のようなDXが実現できます。
- 普段の開発の中ではJestのテストランナーを使い、Jestのメリットを存分に享受
- CIなど特別なタイミングでいつでも任意の実ブラウザ上でテストを実行できる
※いくつか細かい話
Jestのテストコードの中にスナップショットテストが含まれる場合、これは実ブラウザ上で実行できるようになっていないため、そのままではエラーになります。Karmaのエントリーポイントとなるファイルで次の処理を書くことで回避できます。
window['expect'].extend({
// Karmaからの実行時はスナップショットテストは無条件にパス
toMatchSnapshot: () => ({ pass: true })
});
また、JestのconfigでrestoreMocks
やresetMocks
など(テストのたびにモックをリセットする設定)を指定している場合、Jestのテストランナー以外からテストを実行する際はこれが読み込まれないため、指定している内容に応じてグローバルなbeforeEach
を実行して設定を反映する必要があります。これもKarmaのエントリーポイントとなるファイルで実行します。
// `restoreMocks: true`の場合
beforeEach(() => jest.restoreAllMocks());
最後に、これはあまり細かくない話かもしれませんが、実はこの方法ではJestのマニュアルモックという機能が使えなくなります。元々のissueに、「こんなんできました!」と意気揚々と書き込んだら、親切な方が教えてくれました。
私のチームではJasmineからJestに移行し、そもそもJasmineにはマニュアルモックのような機能がなかったため特に問題なかったのですが、すでにJestでマニュアルモックをバリバリ使っているチームがこの方法を取り入れるのは少し難しいかもしれません。
終わりに
この頃流行りのJestを精神の安寧を保ちながら導入できました。
株式会社カブクでは、テスト環境のさらなる改善に共に取り組んでくれるエンジニアを募集しています。
その他の記事
Other Articles
関連職種
Recruit