マイナー・マイナー

隠れた名作の発掘が生きがい。

Sinon.JSを用いたStubの実装例


スポンサードリンク

Sinon.JSとは、スタブやモックなどの実装に役立つJavaScriptのライブラリです。スタブを実装するための関数が充実していて、慣れるとテストがサクサク進められると思います。このSinon.JSを使って下記のような関数のスタブを実装しました。実装できたどうかの検証はQUnitで調べました。

(1) 戻り値ありの関数
(2) 引数がコールバックの関数
(3) 別の関数が応答となる関数


関連記事:
QUnitを用いた単体テストの実装例

導入

下記のSinon.JS公式ページより、「sinon-1.9.0.js」をダウロードします。

http://sinonjs.org/


フォルダ構成は下記のようにしました。「fuga.js」はテスト対象の関数を記述し、「sinon-stub-test.js」にテストコードのサンプルを記述します。「sinon-stub-test.html」はスタブの検証結果を表示します。

フォルダ構成

/
∟qunit
 ∟qunit-1.14.0.css
 ∟qunit-1.14.0.js
∟sinon
 ∟sinon-1.9.0.js
∟test
 ∟hoge.js
 ∟sinon-stub-test.html
 ∟sinon-stub-test.js


sinon-stub-test.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Sinon Stub Test</title>
  <!-- QUnitレポート用CSSをインポート -->
  <link rel="stylesheet" href="../qunit/qunit-1.14.0.css">
  <!-- QUnitをインポート -->
  <script type="text/javascript" src="../qunit/qunit-1.14.0.js"></script>
  <!-- Sinon.JSをインポート -->
  <script type="text/javascript" src="../sinon/sinon-1.9.0.js"></script>
  <!-- テスト対象コードをインポート -->
  <script type="text/javascript" src="./fuga.js"></script>
  <!-- テストコードをインポート -->
  <script type="text/javascript" src="./sinon-stub-test.js"></script>
</head>
<body>
  <!-- テスト結果表示 -->
  <div id="qunit"></div>
</body>
</html>

(1) 戻り値ありの関数

下記の2つの関数をスタブとして作ります。

・引数なし関数(fuga1)
・引数あり関数(fuga2)


まず、対象オブジェクトの関数をスタブとするコードを下記のように書きます。

var stub = sinon.stub(object, "method");


戻り値は下記のように記述します。

stub.returns(obj);


引数は、

stub.withArgs(arg1[, arg2, ...]);


で指定でき、この引数に対応する戻り値を例えば下記のように記述できます。

stub.withArgs(true).returns(false); // 引数がtrueならばfalseを返却


fuga.js

// ----- テスト関数定義 -----
var Fuga = function() {};

// 引数なし関数
Fuga.prototype.fuga1 = function() {
  // 何か処理
  return 0;
};

// 引数あり関数
Fuga.prototype.fuga2 = function(value) {
  // 何か処理
  return 0;
};


sinon-stub-test.js

var fuga;

module("Fugaテスト", {
  // テスト準備
  setup: function () {
    fuga = new Fuga();
  }
});

// テスト開始
test("引数なし関数", function () {
  // スタブ設定
  var stub = sinon.stub(fuga, "fuga1");
  stub.returns(1); // 1を返却
 
  // 検証
  ok(fuga.fuga1() == 1, "OK"); // OK
});

test("引数なし関数", function () {
  // スタブ設定
  var stub = sinon.stub(fuga, "fuga2");
  stub.withArgs(1).returns(10); // 引数が1ならば10を返却
  stub.withArgs(2).returns(20); // 引数が2ならば20を返却
 
  // 検証
  ok(fuga.fuga2(1) == 10, "OK"); // OK
  ok(fuga.fuga2(2) == 20, "OK"); // OK
});

(2) 引数がコールバックの関数

次のコールバック関数を引数とする関数のスタブを考えます。

・引数なしのコールバック関数(fuga3)
・引数ありのコールバック関数(fuga4)


引数なしのコールバック関数は下記メソッドで呼び出すことができます。

stub.callsArg(index);


indexは引数の番号で、第一引数であれば0、第二引数であれば1を指定します。引数なしのコールバック関数は、

stub.callsArgWith(index, arg1, arg2, …);


で呼び出すことができます。arg*の部分にコールバック関数に与える引数を指定します。似たようなメソッドにyields、yieldsOn等があります(これらはindexの指定が省略されたメソッドのようです)。


fuga.js

// ----- テスト関数定義 -----
var Fuga = function() {};

// 引数なしのコールバック関数
Fuga.prototype.fuga3 = function(callbackFunc) {
  // 何か処理
  callbackFunc();
};

// 引数ありのコールバック関数
Fuga.prototype.fuga4 = function(callbackFunc) {
  // 何か処理
  callbackFunc(0);
};


sinon-stub-test.js

var fuga;

module("Fugaテスト", {
  // テスト準備
  setup: function () {
    fuga = new Fuga();
  }
});

test("引数なしのコールバック関数", function () {
  // スタブ設定
  var stub = sinon.stub(fuga, "fuga3");
  stub.callsArg(0); // 第一引数(indexは0)のコールバック関数を呼び出す

  // 検証用コールバック関数の準備
  var result = 0;
  var callbackResult = function () {
    result = 1;
  };

  // 検証
  fuga.fuga3(callbackResult);
  ok(result == 1, "OK"); // OK
});

test("引数ありのコールバック関数", function () {
  // スタブ設定
  var stub = sinon.stub(fuga, "fuga4");
  stub.callsArgWith(0, 10); // 第一引数(indexは0)のコールバック関数(引数は10)を呼び出す

  // 検証用コールバック関数の準備
  var result = 0;
  var callbackResult = function (value) {
    result = value;
  };

  // 検証
  fuga.fuga4(callbackResult);
  ok(result == 10, "OK"); // OK
});

(3) 別の関数が応答となる関数

例えば通信処理などのように、要求(リクエスト)を行う関数と応答(レスポンス)を受け取る関数が分かれていることがあります。この場合、要求関数が呼ばれた場合、応答関数を呼び出すようなスタブを作れば良さそうです。Sinonでは下記のコードで実現できます。

var stub = sinon.stub(Object obj, String methodName, Function func)


methodNameが呼ばれたら、funcを呼び出すという処理です。


fuga.js

// ----- テスト関数定義 -----
var Fuga = function() {
  var _result_fuga5 = 0;
};

// 要求関数(応答はfuga5_2)
Fuga.prototype.fuga5_1 = function() {
  // 何か処理
  // 後続の何かを要求して終わり
};

// 応答関数(要求はfuga5_1)
Fuga.prototype.fuga5_2 = function(result) {
  // 何か処理
  _result_fuga5 = result;
};

// 結果取得関数
Fuga.prototype.getResultFuga5 = function() {
  return _result_fuga5;
};


sinon-stub-test.js

var fuga;

module("Fugaテスト", {
  // テスト準備
  setup: function () {
    fuga = new Fuga();
  }
});

test("別の関数が応答となる関数", function () {
  // 擬似要求処理関数
  var piyo = function() {
    fuga.fuga5_2(10); //応答関数
  }

  // スタブ設定
  var stub = sinon.stub(fuga, "fuga5_1", piyo); //fuga5_1が呼ばれたら、piyoを呼ぶように設定
 
  // 検証
  fuga.fuga5_1();
  ok(fuga.getResultFuga5() == 10, "OK"); // OK
});