mocha を使った npm のユニットテストをブラウザで動かす設定
photo credit: wuestenigel White Cup with Coffee Grains via photopin (license)
mochaとchaiを使ったnpmのユニットテストをブラウザで動かすための設定をご紹介。
mochaにはブラウザで動作させる機能が備わっていますが、テストスクリプト以外にHTMLも用意しなくてはならないので少し億劫なんですよね。
実際には、ほぼ定型のHTMLなのですが、結構情報が少なくて「これで決まり!」みたいなのがない感じ。 コンソールとブラウザでスクリプトを共有したい場合に、問題が出て途方に暮れたこともありました。
ところが最近、ゆる~く試行錯誤を繰り返した結果、自分なりのテンプレートみたいなのが出来上がりつつあるので、ここに書いておきますよっと。
これとは逆に、WEBブラウザ向けESM(ESモジュール)のmochaのスクリプトをNode.jsでテストしたい場合の設定は、以下のページに書いています。
ブラウザでmochaのテストを行う為のHTML
以下はユニットテストをブラウザーで動かすために必要なHTMLファイルです。
test/web-test.html
<!DOCTYPE html> <html> <head> <title>npm run web-test</title> <link rel="stylesheet" href="../node_modules/mocha/mocha.css" /> </head> <body> <h1>npm run web-test</h1> <div id="mocha"></div> <!-- mocha の読み込みとセットアップ --> <script src="../node_modules/mocha/mocha.js"></script> <script> mocha.setup('bdd'); mocha.setup('tdd'); </script> <!-- テストスクリプトを読み込む --> <script src="./web-test.js"></script> <!-- テストの実行 --> <script> localStorage.debug = "*"; //npm debug向けのデバッグログの設定。 mocha.run(); </script> </body> </html>
mochaの公式ページに掲載されているHTMLファイル(→RUNNING MOCHA IN THE BROWSER - mocha)をもとにしており、npm の開発環境にインストール(npm install --save-dev mocha chai
)されているモジュールを利用するように変更しています。
元ファイルでは mocha と chai の外部スクリプトをCDNから読み込んでいますが、mocha は node_modules 以下のファイルを読み込むようにして、chai はJS側で取り込みます(理由は次項で)。
debug モジュールのすゝめ
必須ではありませんが、上の例では見やすいログを出力する debug
モジュールの設定をしています。
ブラウザではF12ツールのコンソールに色がついて見やすくなります。
WebではlocalStorage.debug
に、出力したいロガーの名前をカンマ区切りで指定しておきますが、上の例では "*"
としていますから、すべてのログが表示されます(mochaもdebugを利用しています)。
ブラウザで動作するmochaのテストスクリプト
chai は テストスクリプトで require する。
前述のように、元ファイルでは chai をSCRIPTタグで読み込んでいますが、自分的にはコンソールから実行するものと同じように、テストスクリプトで取り込むようにしています。 こうしておくことで、コンソールで実行するテストスクリプトのうち Node.js特有の機能を使っていない物をブラウザでも実行できるようになるからです。
しかし、これによってテストスクリプトをバンドラーでコンパイルしなくてはなりません。 これについては後述しますが、設定なしの Parcel を使えば簡単です。
ユーザー操作を待つテストの書き方
テストの実行時に、ボタンのクリックなどの操作を待つ必要がある場合は、その操作で解決する非同期関数を用意し、テストの中から呼び出しますようにします。 この場合、mochaのテストのタイムアウトを無効にしておく必要があります。
以下の例は、ボタンがクリックされてからテストが実行されます。
例)ユーザー操作を待つテストスクリプト
"use strict"; const assert = require("chai").assert; /** * ボタンを作って押されたら解決するPromiseを返す。 * Promiseは30秒後にリジェクトされる。 * @returns {Promise} Promiseを返す。 */ const buttonClicked = (() => (new Promise( (resolve, reject) => { const button = document.createElement("BUTTON"); button.innerHTML = "Click to go"; button.addEventListener("click", () => resolve() ); document.body.appendChild(button); setTimeout(() => { document.body.removeChild(button); reject(new Error("The user operation timeout")); }, 30000); }))); //テストターゲット const foo = () => "bar"; //ユニットテスト describe("foo", () => { it("should be bar", async () => {//非同期関数とします await buttonClicked(); //ボタンが押されるまで待つ関数 assert.equal( foo(), "bar" ); }).timeout(0); //タイムアウト無効 });
- 待ち時間が30秒を越えるとエラーを投入するのでテストは失敗します。
- クリックするためのボタンを動的に追加していますが、あらかじめHTMLに書いておいても構いません。
npmのscriptsを設定する
テストに限らず、何らかのコマンドの実行は npm の scripts
を使うと便利です。
node_modules 以下にあるモジュールのコマンドはパスの指定をせずに使えますから。
ここでは一例として、npm run web-test
でブラウザでのテストを実行するよう設定し、
npm test
または npm run test
で通常のコンソールでのテストを実行するようにしています。
この辺はお好みに応じて変更してください。
package.json(一部):
"scripts": { "test": "mocha", "web-test": "parcel test/web-test.html --open", ・・・ },
Parcel のすゝめ
ブラウザでのテストは、Parcel だけで 「バンドル」 → 「Webサーバー起動」 →「 ページを開く」 までを一気にやっています。 Parcelめっちゃ便利です。ブラウザーでのテストのためだけに使っても良いかも?って思っています。 BrowserifyやWeb-Packでは、いろいろ細かな設定が必要になるはずです。
ページを開いたときの実行時エラー 「regeneratorRuntime is not defined」を解消する
Parcelを使うとBabelも使うことになりますが、そのままだとブラウザでの実行時に以下のようなエラーが出ることがあります。
Uncaught ReferenceError: regeneratorRuntime is not defined at Suite.<anonymous> (test-script.js:38) at Object.create (mocha.js:720) at context.describe.context.context (mocha.js:532) at Suite.<anonymous> (test-script.js:7) at Object.create (mocha.js:720) at context.describe.context.context (mocha.js:532) at Suite.<anonymous> (test-script.js:6) at Object.create (mocha.js:720) at context.describe.context.context (mocha.js:532) at Object.parcelRequire.transworker.js.chai (test-script.js:5)
このエラーはBabelで変換対象となるような比較的新しいコードがあるときに発生するように思います。
解消するには、以下のような babel-polyfill だけをインポートするスクリプトを用意して、HTMLのSCRIPTで、テストスクリプトよりも前に読み込みます。ポリフィルはWebページから一度だけ参照すべきなので、このような対処になります。
test/babel-polyfill.js
require("babel-polyfill");
test/web-test.html
<!-- ポリフィルを読み込む --> <script src="./babel-polyfill.js"></script> <!-- テストスクリプトを読み込む --> <script src="./web-test.js"></script>
コンソールでブラウザ向けテストを除外する
細かなことですが、コンソールでもテストを行っている場合は、 ブラウザ特有の機能を使っているテストスクリプトを除外してやる必要があります。
これは、mocha.opt というファイルで行います。 以下の例では、testディレクトリ内のファイル名が'web-'で始まるファイルを除外しています。
test/mocha.opt
test/!(web-)*.js