Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

testdouble.jsによるローカル環境でのスタブ作成

More than 1 year has passed since last update.

はじめに

クライアントサイドで実行されるJavaScriptの開発中、未完成なモジュールのメソッドやAPIリクエストを行う処理があると、仮に動作させるためのスタブが必要になる。
リモートサーバーを用意してAPIスタブを作ったりしても良いが、テストライブラリのtestdouble.jsを使えば、ローカル環境上で動くスタブを簡単かつ柔軟に作成できる。

インストール

npm install -D testdouble

次にスタブ設定用のファイルを用意する。

stub.js
var td = require('testdouble');

クライアントサイドで実行する処理をスタブ化するため、webpackなどのモジュールバンドラーを用いて、開発用ビルドではこのファイルもバンドルする。
本番用のビルドからは省く必要があるので、エントリポイントとなるファイルには、ビルド環境に応じてスタブを含めるかの分岐を記述しておく。

if(NODE_ENV !== 'production') {
  require('./stub.js'); // testdouble.jsのスタブ設定用ファイル
}

参考:webpackで環境変数をモジュールに渡す

使い方

メソッド

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.whendelayオプションを設定すると、非同期通信のレスポンス待ちが再現できるので、待ち時間中の画面状態も確認できる。

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()ではNumberStringといった引数の型を指定できる。

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の仕様変更によって突然動かなくなるといった状況を防ぎ、開発効率を上げることができる。
一方で、外部の仕様が変更されても関係なく動作してしまうため、合わせてスタブも更新するのを忘れずに。

hal9188
yahoo-japan-corp
Yahoo! JAPAN を運営しています。
https://www.yahoo.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away