CodeZine(コードジン)

特集ページ一覧

JavaScriptでスパイ、スタブ、モックなどのテストダブルを行う

JavaScriptでテストを書こう! 第4回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2014/05/08 14:00
目次

スタブ

 スタブは、オブジェクトを置き換え、引数や戻り値を指定します。例えばAjaxでのサーバ呼び出しやDBなどの外部リソースや、複数の関数を呼び出しているような関数をテストする際に、呼び出し先の関数をスタブで上書きし、戻り値を個別に指定してあたかも外部リソースが呼ばれたかのようにテストをできます。スタブは前項で説明したスパイの関数も使用することができますが、スパイとの違いは関数に対してスタブを作成すると、オブジェクトが上書きされてしまうため、元の関数が呼び出されません。

 リスト2-01はバリデーションのテストを行っています。Userクラスのvalidメソッドは、UserNameとMailAddressクラスのvalidメソッドを呼び出した組み合わせの結果を返しているため、それぞれの戻り値を偽装し、組み合わせのテストを行います。

[リスト2-01]バリデーションのテスト(Sinon.js.html)
describe("ユーザ情報のバリデーションのテスト", function() {
  it("バリデーションのテスト", function() {
    /** 事前準備 */
    var userName = new UserName('Koike Naoki');
    var mailAddress = new MailAddress('koike@koikenaoki.com');
    var user = new User(userName, mailAddress);

    // validメソッドをスタブにする 
    var userNameStub = sinon.stub(userName, 'valid');
    var mailAddressStub = sinon.stub(mailAddress, 'valid');

    // 1回目に呼び出された際の戻り値  
    userNameStub.onFirstCall().returns(true);
    mailAddressStub.onFirstCall().returns(true);

    // 2回目に呼び出された際の戻り値
    userNameStub.onSecondCall().returns(true);
    mailAddressStub.onSecondCall().returns(false);

    // 3回目に呼び出された際の戻り値
    userNameStub.onThirdCall().returns(false);
    mailAddressStub.onThirdCall().returns(true);

    /** 実行 */
    var actual1 = user.valid(userName, mailAddress);
    var actual2 = user.valid(userName, mailAddress);
    var actual3 = user.valid(userName, mailAddress);

    /** 確認 */
    expect(actual1).to.be.true;
    expect(actual2).to.be.false;
    expect(actual3).to.be.false;
  });
});
[リスト2-02]テスト対象のバリデーション関数(user.js)
function User(userName, mailAddress) {
    this.userName = userName;
    this.mailAddress = mailAddress;
}
User.prototype.valid = function() {
    if (!this.userName.valid() || !this.mailAddress.valid()) {
        return false;
    }
    return true;
};

 ①では、スタブオブジェクトを生成しています。UserNameとMailAddressクラスオブジェクトのvalidメソッドをスタブにしています。スタブオブジェクトの生成方法は、表2-01のとおりです。

[表2-01]スタブオブジェクトの生成
呼び出し方法 概要
var stub = sinon.stub(); 空のスタブオブジェクトを生成。
主にコールバックで渡す無名関数などに対して使用する。
var stub = sinon.stub(object, "method"); 特定の関数に対してスタブオブジェクトを作成する。
var stub = sinon.stub(object, "method", func); 特定の関数に対してスタブオブジェクトを作成し、
指定した関数で処理を上書きします。
var stub = sinon.stub(obj); 対象オブジェクトのメソッドをすべてスタブにする。

 ②では、1回目~3回目まで呼び出された際の戻り値を指定しています。スタブはスパイオブジェクトが持っている関数は基本的に持っていますが、その他の主な関数は表2-02のとおりです。

[表2-02]主なスタブオブジェクトの関数
呼び出し方法 概要
stub.returns(obj); 呼び出し時のリターン値を指定する。
stub.throws(obj); 呼び出し時に指定したExceptionを発生させる。
stub.onCall(n); n回目のスタブオブジェクトを返す。
stub.onFirstCall(); 1回目のスタブオブジェクトを返す。
stub.onSecondCall(); 2回目のスタブオブジェクトを返す。

モック

 モックはスタブと混同しやすいのですが、最も違うのは、事前に予測しておくという点です。スタブは、あらかじめオブジェクトを置き換えておくだけで、それが呼び出されなくても関係ありません。モックの場合は、テスト対象の処理実行前に呼び出させる方法を指定しておき、処理実行後に指定通りに実行されたかチェックをします。そのとおりに実行されなければ、テストはエラーが発生したものとして扱われます。モックは、実行後に想定通りに呼ばれたかどうかチェックを行うため、個別のアサート文が必要になるような場合は、通常スタブを使用します。

 リスト3-01は、バリデーションエラー時のテストです。メールアドレスのバリデーションが実行されるか確認し、登録関数が呼ばれないことを確認しています。

[リスト3-01]バリデーションのテスト(Sinon.js.html)
describe("ユーザ登録時のバリデーションのテスト", function() {
  it("バリデーションエラー時に登録処理が呼ばれていないか", function() {
    /** 事前準備 */
    var userName = new UserName('Koike Naoki');
    var mailAddress = new MailAddress('こいけ@koikenaoki.com');
    var user = new User(userName, mailAddress);

    // モックの準備 
    var mailAddressMock = sinon.mock(mailAddress);
    var userRepositoryMock = sinon.mock(UserRepository);

    // 実行の予測 
    // mailAddressのバリデーションは1回呼び出して
    // ここで作成したオブジェクトで呼び出されている 
    mailAddressMock.expects("valid").once().on(mailAddress);
    // バリデーションエラーなので登録処理は呼び出されない 
    userRepositoryMock.expects("register").never();

    /** 実行 */
    UserService.register(user, function() {
    });

    /** 確認  */
    expect(userRepositoryMock.verify()).to.be.true;
    expect(mailAddressMock.verify()).to.be.true;
  });
});
[リスト3-02]テスト対象のバリデーション関数(user.js)
var UserService = {
  register: function(user, callback) {
    if (!user.valid()) {
      return;
    }
    UserRepository.register(user, callback);
  }
};

 はモックオブジェクトを作成し、は実行の予測をしています。モックオブジェクトは表3-01のような機能が用意されています。実行内容を予測しテスト対象関数を実行後、のようにverify関数を実行して、予測通りに実行されたかを評価します。

 関数実行の予測は、expects関数に予測対象のメソッド名を指定し、取得したexpectationオブジェクトに対して関数を実行しながら指定していきます。expectationオブジェクトの持つ機能は表3-02のとおりです。

[表3-01]モックオブジェクトの機能
呼び出し方法 概要
var mock = sinon.mock(obj); モックオブジェクトを生成する。
var expectation = mock.expects("method"); 指定したmethodのexpectationオブジェクトを生成する。
mock.restore(); モックを削除する。
mock.verify(); モックをチェックする。
[表3-02]expectationオブジェクトの機能
呼び出し方法 概要
expectation.atLeast(number); 最低呼び出し回数を指定する。
expectation.atMost(number); 最高呼び出し回数を指定する。
expectation.never(); 呼び出されないことを指定する。
expectation.once(); 1回呼び出されることを指定する。
expectation.twice(); 2回呼び出されることを指定する。
expectation.exactly(number); 引数のnumber回数呼び出されることを指定する。
expectation.withArgs(arg1, arg2, ...); 指定した引数で呼び出されることを指定する。
expectation.on(obj); 引数に指定したオブジェクトで関数が呼び出されることを指定する。
expectation.verify(); 指定した通りに呼び出されたかをチェックする。

  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:JavaScriptでテストを書こう!

著者プロフィール

  • WINGSプロジェクト 安西剛 (ヤスニシ ツヨシ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

あなたにオススメ

All contents copyright © 2005-2022 Shoeisha Co., Ltd. All rights reserved. ver.1.5