はじめに
クライアントサイドで実行されるJavaScriptの開発中、未完成なモジュールのメソッドやAPIリクエストを行う処理があると、仮に動作させるためのスタブが必要になる。
リモートサーバーを用意してAPIスタブを作ったりしても良いが、テストライブラリのtestdouble.jsを使えば、ローカル環境上で動くスタブを簡単かつ柔軟に作成できる。
インストール
npm install -D testdouble
次にスタブ設定用のファイルを用意する。
var td = require('testdouble');
クライアントサイドで実行する処理をスタブ化するため、webpackなどのモジュールバンドラーを用いて、開発用ビルドではこのファイルもバンドルする。
本番用のビルドからは省く必要があるので、エントリポイントとなるファイルには、ビルド環境に応じてスタブを含めるかの分岐を記述しておく。
if(NODE_ENV !== 'production') {
require('./stub.js'); // testdouble.jsのスタブ設定用ファイル
}
使い方
メソッド
td.replace
を用いると、指定したメソッドを置き換えられる。
var text = {
show: function(str) { console.log(str); }
};
text.show('test'); // test
td.replace(text, 'show', (str) => { console.log(`show:${str}`) });
text.show('test'); // show:test
また、メソッドに渡す引数に応じて返り値を変えたい場合は、td.when
を使う。
var browser = {
isIE: function(ua) { return /Trident|MSIE/.test(ua); }
};
browser.isIE('Trident'); // true
browser.isIE('MSIE'); // true
td.replace(browser, 'isIE');
td.when(browser.isIE('Trident')).thenReturn(true);
td.when(browser.isIE('MSIE')).thenReturn(false);
browser.isIE('Trident'); // true
browser.isIE('MSIE'); // false
API
testdouble.jsには擬似XHRの機能はないが、td.replace
でAPIリクエストを投げるメソッドを置き換えることで、擬似的にAPIのスタブを実現できる。
var ajax = {
get: function(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = function(e) {
if(this.readyState === 4) {
if(this.status === 200) {
var res = JSON.parse(this.responseText);
callback(res);
}
}
}
xhr.send(null);
}
};
td.replace(ajax, 'get');
td.when(ajax.get('http://api.example.com/get')).thenCallback(JSON.parse('{"status":"success"}'));
ajax.get('http://api.example.com/get', function(res) {
console.log(res); // {"status":"success"}
});
また、td.when
にdelay
オプションを設定すると、非同期通信のレスポンス待ちが再現できるので、待ち時間中の画面状態も確認できる。
td.when(hoge.get('http://api.example.com/get'), { delay: 500 }).thenCallback(JSON.parse('{"status":"success"}'));
引数
渡す引数に応じて挙動を変えたい場合は、td.matchers
を用いる。
何でも
td.matchers.anything()
ではどんな引数でもOKとなる。ただし引数の数自体は厳密に渡す必要がある。
var hoge = td.function();
td.when(hoge(td.matchers.anything())).thenReturn('fuga');
hoge('test'); // fuga
hoge(true); // fuga
hoge(9); // fuga
hoge(); // undefined
hoge('test', true); // undefined
型指定
td.matchers.isA()
ではNumber
やString
といった引数の型を指定できる。
var hoge = td.function();
td.when(hoge(td.matchers.isA(Number))).thenReturn('fuga');
hoge(9); // fuga
hoge('nine'); // undefined
〜を含む
td.matchers.contains()
では特定の文字列を含むかの指定ができる。
var hoge = td.function();
td.when(hoge(td.matchers.contains('nine'))).thenReturn('fuga');
hoge('nine'); // fuga
hoge('none'); // undefined
また、正規表現も使える。
var hoge = td.function();
td.when(hoge(td.matchers.contains(/ni/))).thenReturn('fuga');
hoge('nine'); // fuga
hoge('none'); // undefined
この他にもtd.matchers.contains()
は配列やオブジェクトの一致判定にも使える。それらの使い方は公式ドキュメントを参照。
終わりに
ローカル環境でスタブを構築できることにより外部への依存が少なくなるため、開発中のモジュールやスタブ用のAPIの仕様変更によって突然動かなくなるといった状況を防ぎ、開発効率を上げることができる。
一方で、外部の仕様が変更されても関係なく動作してしまうため、合わせてスタブも更新するのを忘れずに。