要旨
- ブラウザに固有のグローバル変数(
window
,document
など)1 を使うコードをNode.jsで走らせるためにbrowser-env
を使う -
ava
を使ったテストにbrowser-env
を使用したサンプルを紹介する
背景・課題
ブラウザで動かすことを前提に書いたJavaScriptをNode.jsで走らせたいことがある。例えばユニットテストがそうだ。
次のようなサンプルを用意した。
このサンプルはlocationのhashを表示する、という操作を実行している(テストのために遠回りに書いている)。ビルドされたファイルをブラウザで開くと期待通り動作する確認できる。
しかし次のような関数を AVA を使ってユニットテストを実行した時に問題が発生した。
const getUrlHash = href => {
return new URL(href).hash;
};
export { getUrlHash as default };
テストコードは次の通りだ。
import test from 'ava';
import getUrlHash from '../src/url';
test('getUrlHash should return the hash of the URL', t => {
const actual = getUrlHash('http://example.com/path/to?key=value#xyz');
const expected = '#xyz';
t.is(expected, actual);
});
残念ながらブラウザで問題なく動いたこのコードも、ava実行時は URL
が定義されていないとしてエラーになってしまう。
$ ava
1 failed
url › getUrlHash should return the hash of the input URL
/path/to/try-browser-env/src/url.js:7
Error thrown in test:
ReferenceError {
message: 'URL is not defined',
}
getUrlHash (src/url.js:7:3)
Test.fn (test/url.test.js:5:18)
原因はNode.js環境とブラウザ環境の違いである。Node.jsには URL
という関数はグローバルに定義されていないが、(一部の)ブラウザにおいては URL
は定義されている。
// node cliで確認する
$ node
> URL
ReferenceError: URL is not defined
...
// ChromのDeveloper Toolのコンソールで確認する
> URL
> ƒ URL() { [native code] }
解決方法
AVAによると browser-env を使うことで解決できる。使い方は AVAのレシピ にあるがポイントだけ掻い摘んでおこう。
インストール
$ npm install --save-dev browser-env
// or
$ yarn add --dev browser-env
セットアップ
test/helpers/ ディレクトリに以下のようなファイルを用意する。
import browserEnv from 'browser-env';
browserEnv();
package.jsonの ava.require
にこのヘルパーファイルを設定する。
{
"ava": {
"require": [
"./test/helpers/setup-browser-env.js"
]
}
}
これでAVAのテスト実行時にブラウザ環境を模倣することができたので URL
の ReferenceError
になることはない。もちろん src/ ディレクトリのファイルは変更しておらず、ブラウザでも問題なく動く。
※補足
上記のように browserEnv への引数を空にすると全てのブラウザ固有のグローバル変数がNode.jsのグローバルスコープに追加されてしまう。ブラウザ環境を模倣するには良いが、把握していない/必要としないグローバル変数を作るのは健全ではない。使用するものだけに限定すると良いだろう。
import browserEnv from 'browser-env';
browserEnv(['URL']);
また個別のテストファイルに対してブラウザ固有のグローバル変数を導入したいときは、各テストファイルに記載しても問題ない。
import test from 'ava';
import getUrlHash from '../src/url';
import browserEnv from 'browser-env';
browserEnv(['URL']);
test('getUrlHash should return the hash of the input URL', t => {
const actual = getUrlHash('http://example.com/path/to?key=value#xyz');
const expected = '#xyz';
t.is(expected, actual);
});
その他のブラウザ変数
browser-envが用意するのは URL
だけではない。 document
や location
などのブラウザ固有のグローバル変数もモックしてくれる。
import browserEnv = require('browser-env');
browserEnv(['location'], {url: "http://example.com/path/to?key=value#xyz"});
location.pathname
//'/path/to'
location.hash
// '#xyz'
browser-envは jsdom を使っているので、browserEnv
の第2引数の詳細は JSDOM
を参考にするとよい。