ES6で書かれたUserscriptでchaiとmochaを使ったテストを走らせるための小技
github.comのcommit一覧ページで、2点のcommit hashからcompareページに簡単に遷移できる Userscript を3年ほど前に開発し、最近ES6を使ってほぼフルスクラッチで書き換えた。
その際に chai
と mocha
を使ったユニットテストを書けるように整備した。通常のjsファイルとは異なり、気をつけないと「Userscriptは動くがテストは動かない」、「Userscriptは動かないがテストは動く」といった事態に陥る。
このエントリでは、Userscriptをtestableにするために実装上気をつけた点を紹介する。
Userscript側
テストしたい関数を1つのclassに集約する
Userscriptは、ブラウザに組み込むことで何らかの補助的動作を可能にするツールである。この「補助的動作」の根幹に当たる部分を class
に切り出し、それをテストケースの対象としておくと見通しが良くなる。
冒頭に上げた github-commitdiff-viewer
では、
などといったロジックを CommitDiffUIManager
というclassに集約させ、このclassをテスト対象とした。
Userscriptだけで動かしたい処理とテストだけで動かしたい処理を分岐させる
Userscriptとして動かしつつ、テストも実行可能なように記述するために下記のような実装を行った。
class TestTargetClass { // Userscriptの機能の根幹部分を実装 } if (typeof window === 'object') { let testTargetClass = new TestTargetClass(...); testTargetClass.executeSomething(); } else { module.exports = TestTargetClass; }
module.exports
はテストだけで動かしたい部分で、ここをUserscript上で実行してしまうとエラーが発生してしまう。
テスト側で global.window
オブジェクトをUserscriptのrequire後にmockすることで、「Userscriptをテスト側でrequireしたときにはwindowオブジェクトがない」という状況を作り出すことが出来る。
let TestTargetClass = require(path.join(__dirname, '..', 'someuserscript.user.js')); // この時点ではwindowは未定義 global.window = ...
この挙動の差を利用して、 if (typeof window === 'object')
が true
のときはUserscriptのみで動かしたい実装を記述し、 false
のときはテストのみで動かしたい実装を記述することが出来る。
テスト側
jsdomを使ってUserscriptが読み込まれるページのdocumentをmockする
let jsdom = require('jsdom'); const dom = new JSDOM(` // Userscriptが読み込まれるページのdocumentのHTMLのうち、 // テストに必要な部分をべた書きする `); global.window = dom.window; global.$ = require('jquery');
前の項目で global.window
をUserscriptのrequire後に記述するとUserscriptとテストで挙動を分けることが出来る点を紹介した。
global.window
にjsdomを活用することで、Userscriptが読み込まれるページをモックでき、「DOMを探索して何かする」といった挙動の関数についてもテストが可能になる。