MochaとChaiでなんでもテスト ~ブラウザ用JavaScript編~
MochaとChaiでなんでもテスト 6本目です。
今回は、ブラウザでしか動かないJavaScriptのコードをMochaとChaiでテストしてみます。
前書き
昨日調べてて知ったのですが、Mochaはブラウザでも動かせるんですね。普通に公式サイトに書いてありました。
ブラウザで動くと言っても、MochaのためにHTMLを一枚用意してその上で動かす感じなので、あくまでモジュールテストの用途になると思います。でもブラウザでしか動かないようなコード、例えばDOMいじりだとか Blob
周りだとかのテストができるのは嬉しいです。
それと特筆すべき点として、ブラウザで動かすMochaとChaiは unpkg.com から読み込むので npm install
が不要です。Node.jsが入ってないPCでも動かせるので、ちょっとMochaとChaiを試してみたい人にもいいかもしれないです。
以下、この記事では公式サイトを参考に、適当なCanvasのユーティリティ関数をテストしてみます。
サンプル
サンプルのディレクトリ構成は以下の通りです。
...
├ tests
│ ├ runner.html (テスト実行用HTML)
│ └ browser.js (テストコード)
└ code.js (テスト対象モジュール)
上に書いた通り、今回は npm install
不要なので node_modules
はありません。
各ファイルの内容は以下の通りにします。
/**
* Canvasを作成
* @param {Number} width
* @param {Number} height
* @return {HTMLCanvasElement}
*/
function createCanvas(width, height) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas;
}
/**
* Canvasの座標からインデックスを算出する
* @return {Number}
*/
function coordsToIndex(canvas, x, y) {
if (x >= canvas.width) {
throw RangeError('X座標がCanvasの範囲外です。');
} else if (y >= canvas.height) {
throw RangeError('Y座標がCanvasの範囲外です。');
}
return y * canvas.width + x;
}
const expect = chai.expect; // chaiとmochaはrunner.htmlでグローバルに読み込み済み
describe('ブラウザでしか動かないテスト', function() {
it('createCanvas', function() {
const canvas = createCanvas(100, 200);
expect(canvas).a('HTMLCanvasElement');
expect(canvas.width).equal(100);
expect(canvas.height).equal(200);
});
it('coordsToIndex (正常)', function() {
const canvas = createCanvas(3, 2);
expect(coordsToIndex(canvas, 2, 0)).equal(2);
expect(coordsToIndex(canvas, 0, 1)).equal(3);
expect(coordsToIndex(canvas, 1, 1)).equal(4);
expect(coordsToIndex(canvas, 999, 999)).equal('※エラーになります');
});
it('coordsToIndex (例外)', function() {
const canvas = createCanvas(3, 2);
expect(() => { coordsToIndex(canvas, 3, 0); }).throw(RangeError);
expect(() => { coordsToIndex(canvas, 0, 2); }).throw(RangeError);
});
});
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>Mocha Tests</title>
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" /> <!-- テスト結果表示に使用されるCSS -->
</head>
<body>
<!-- テスト結果表示エリア -->
<div id="mocha"></div>
<!-- MochaとChai読み込み -->
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script src="https://unpkg.com/chai/chai.js"></script>
<!-- Mocha設定 -->
<script class="mocha-init">
mocha.setup('bdd'); // BDD用にセットアップする (describeとかitが使えるようになる)
mocha.checkLeaks(); // テスト実行中にグローバル変数が追加されたらエラー終了させる
</script>
<!-- テスト対象モジュール、テストコード読み込み -->
<script src="../code.js"></script>
<script src="browser.js"></script> <!-- テストコード複数あるなら更に追記可 -->
<!-- テスト実行 -->
<script class="mocha-exec">
mocha.run();
</script>
</body>
</html>
一番特徴的なのは tests/runner.html
です。tests/runner.html
をブラウザで開くことでテストが実行され、結果が画面上に表示されます。
テストの再実行は tests/runner.html
のリロードで行えます。
ただし今回はブラウザを使っているので、テスト対象モジュールまたはテストコードを修正してリロードしてもキャッシュが残ってる場合があります。常にスーパーリロード (Chromeなら Ctrl + F5) するか、キャッシュを無効にするなどして対策してください。
また、ここでは公式のサンプルと同様に mocha.checkLeaks()
を有効にしてます。これを有効にしておくと、テスト実行中にグローバル変数が追加された時に global leaks detected
エラーを吐くようになります。予期せぬグローバル汚染が検知できるので、基本的には有効にするのが良いと思います。
が、筆者環境ではChromeの拡張機能が勝手にグローバル変数を追加して、テストと関係無いところで以下のようなエラーを吐いたりしてました。
Error: global leaks detected: '__REACT_DEVTOOLS_COMPONENT_FILTERS__', '__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__'
今のところ『React Developer Tools』と『Vue.js devtools』が怒られてるのを見たので、入れてる方はテスト中は無効にするとかしておいた方がいいかもしれません。
サンプル (モジュール版)
上記のサンプルは、テスト対象モジュールをグローバルで読み込んでしまってます。気持ち悪いので、モジュール版サンプルも作ってみました。
ただしこちらはJavaScriptファイル内から別のファイルを読み込む関係上、tests/runner.html
をURL file:///C:/~
で開いてるとCORS制約にひっかかってしまいます。 これを回避するには tests/runner.html
をサーバー上で動かす必要があります。
というわけで、今回は [http-server](https://www.npmjs.com/package/http-server)
をインストールしてテスト用のローカルサーバーも一緒に立てるようにします。
npm install --save-dev http-server
簡単にテスト実行できるように、http-server
を起動しつつ tests/runner.html
をブラウザで開くnpmスクリプト『browser-test
』を用意します。
{
...
"scripts": {
"browser-test": "http-server ./ --port 8080 -c-1 -o /tests/runner.html"
},
...
}
browser-test
コマンドの詳細は以下の通りです。
http-server ./
... プロジェクト直下 (./
) を公開フォルダとして指定してhttp-server
を起動する。--port 8080
... ローカルサーバーをポート8080で起動する。-c-1
... ファイルをキャッシュしないようにする。-o /tests/runner.html
... ローカルサーバー起動後、ブラウザでhttp://localhost:8080/tests/runner.html
を開く。
そしたら、テストプログラムもモジュール用に修正していきます。
ディレクトリ構成は同じですが、各ファイルの内容が少しずつ異なります。以下、コメントで★印をつけてるところが差分になります。
/**
* Canvasを作成
* @param {Number} width
* @param {Number} height
* @return {HTMLCanvasElement}
*/
function createCanvas(width, height) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas;
}
/**
* Canvasの座標からインデックスを算出する
* @return {Number}
*/
function coordsToIndex(canvas, x, y) {
if (x >= canvas.width) {
throw RangeError('X座標がCanvasの範囲外です。');
} else if (y >= canvas.height) {
throw RangeError('Y座標がCanvasの範囲外です。');
}
return y * canvas.width + x;
}
export { createCanvas, coordsToIndex }; // ★モジュールなのでexportする
const expect = chai.expect; // chaiとmochaはrunner.htmlでグローバルに読み込み済み
import { createCanvas, coordsToIndex } from '../code.js'; // ★テスト対象モジュールはここでimportする
describe('ブラウザでしか動かないテスト', function() {
it('createCanvas', function() {
const canvas = createCanvas(100, 200);
expect(canvas).a('HTMLCanvasElement');
expect(canvas.width).equal(100);
expect(canvas.height).equal(200);
});
it('coordsToIndex (正常)', function() {
const canvas = createCanvas(3, 2);
expect(coordsToIndex(canvas, 2, 0)).equal(2);
expect(coordsToIndex(canvas, 0, 1)).equal(3);
expect(coordsToIndex(canvas, 1, 1)).equal(4);
expect(coordsToIndex(canvas, 999, 999)).equal('※エラーになります');
});
it('coordsToIndex (例外)', function() {
const canvas = createCanvas(3, 2);
expect(() => { coordsToIndex(canvas, 3, 0); }).throw(RangeError);
expect(() => { coordsToIndex(canvas, 0, 2); }).throw(RangeError);
});
});
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>Mocha Tests</title>
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" /> <!-- テスト結果表示に使用されるCSS -->
</head>
<body>
<!-- テスト結果表示エリア -->
<div id="mocha"></div>
<!-- MochaとChai読み込み -->
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script src="https://unpkg.com/chai/chai.js"></script>
<!-- Mocha設定 -->
<script class="mocha-init">
mocha.setup('bdd'); // BDD用にセットアップする (describeとかitが使えるようになる)
mocha.checkLeaks(); // テスト実行中にグローバル変数が追加されたらエラー終了とする
</script>
<!-- ★テストコードを type="module" で読み込み -->
<script type="module" src="browser.js"></script> <!-- テストコード複数あるなら更に追記可 -->
<!-- ★type="module" でテスト実行 -->
<script type="module" class="mocha-exec">
mocha.run();
</script>
</body>
</html>
これで、用意しておいたnpmスクリプトを以下のコマンドで叩けば、ブラウザで http://localhost:8080/tests/runner.html
が開いてテストが実行されます。
npm run browser-test
こっちの方が tests/runner.html
でテスト対象モジュールを読み込まずに済むので、Node.jsでMochaを使う時と近い感じになりますね。
以上
Mochaのこの機能を使ったテストは今やってる最中なので、もっと何かあったら追記します。
次回はたぶんAWS Lambda編です。